diff --git a/m2e-dev.target b/m2e-dev.target
new file mode 100644
index 0000000000..4c79ff3e34
--- /dev/null
+++ b/m2e-dev.target
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.eclipse.m2e.pde.feature/build.properties b/org.eclipse.m2e.pde.feature/build.properties
new file mode 100644
index 0000000000..64f93a9f0b
--- /dev/null
+++ b/org.eclipse.m2e.pde.feature/build.properties
@@ -0,0 +1 @@
+bin.includes = feature.xml
diff --git a/org.eclipse.m2e.pde.feature/feature.xml b/org.eclipse.m2e.pde.feature/feature.xml
new file mode 100644
index 0000000000..84e90b0648
--- /dev/null
+++ b/org.eclipse.m2e.pde.feature/feature.xml
@@ -0,0 +1,49 @@
+
+
+
+
+ [Enter Feature Description here.]
+
+
+
+ [Enter Copyright Description here.]
+
+
+
+ [Enter License Description here.]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.m2e.pde.feature/pom.xml b/org.eclipse.m2e.pde.feature/pom.xml
new file mode 100644
index 0000000000..d537998bb5
--- /dev/null
+++ b/org.eclipse.m2e.pde.feature/pom.xml
@@ -0,0 +1,23 @@
+
+
+
+ 4.0.0
+
+
+ org.eclipse.m2e
+ m2e-core
+ 1.16.0-SNAPSHOT
+
+
+ org.eclipse.m2e.pde.feature
+ eclipse-feature
+ 1.17.0-SNAPSHOT
+
diff --git a/org.eclipse.m2e.pde.ui/META-INF/MANIFEST.MF b/org.eclipse.m2e.pde.ui/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..8e3b74b1d6
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/META-INF/MANIFEST.MF
@@ -0,0 +1,18 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: PDE Integration UI
+Bundle-SymbolicName: org.eclipse.m2e.pde.ui;singleton:=true
+Bundle-Version: 1.17.0.qualifier
+Automatic-Module-Name: org.eclipse.m2e.pde.ui
+Bundle-RequiredExecutionEnvironment: JavaSE-11
+Require-Bundle: org.eclipse.core.runtime;bundle-version="3.19.0",
+ org.eclipse.jface,
+ org.eclipse.pde.ui;bundle-version="3.12.0",
+ org.eclipse.equinox.frameworkadmin;bundle-version="2.1.400",
+ biz.aQute.bndlib;bundle-version="5.1.2",
+ org.eclipse.m2e.maven.runtime,
+ org.eclipse.m2e.core,
+ org.eclipse.m2e.pde,
+ org.eclipse.pde;bundle-version="3.13.1200"
+Bundle-ActivationPolicy: lazy
+Bundle-Activator: org.eclipse.m2e.pde.ui.Activator
diff --git a/org.eclipse.m2e.pde.ui/build.properties b/org.eclipse.m2e.pde.ui/build.properties
new file mode 100644
index 0000000000..6c480f39f1
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/build.properties
@@ -0,0 +1,6 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml,\
+ icons/
diff --git a/org.eclipse.m2e.pde.ui/icons/error_st_obj.gif b/org.eclipse.m2e.pde.ui/icons/error_st_obj.gif
new file mode 100644
index 0000000000..0bc60689c6
Binary files /dev/null and b/org.eclipse.m2e.pde.ui/icons/error_st_obj.gif differ
diff --git a/org.eclipse.m2e.pde.ui/icons/jar_obj.gif b/org.eclipse.m2e.pde.ui/icons/jar_obj.gif
new file mode 100644
index 0000000000..2fa1d777bc
Binary files /dev/null and b/org.eclipse.m2e.pde.ui/icons/jar_obj.gif differ
diff --git a/org.eclipse.m2e.pde.ui/icons/m2.gif b/org.eclipse.m2e.pde.ui/icons/m2.gif
new file mode 100644
index 0000000000..4b0c0589d8
Binary files /dev/null and b/org.eclipse.m2e.pde.ui/icons/m2.gif differ
diff --git a/org.eclipse.m2e.pde.ui/icons/new_m2_project_wizard.gif b/org.eclipse.m2e.pde.ui/icons/new_m2_project_wizard.gif
new file mode 100644
index 0000000000..8f18a52b25
Binary files /dev/null and b/org.eclipse.m2e.pde.ui/icons/new_m2_project_wizard.gif differ
diff --git a/org.eclipse.m2e.pde.ui/icons/show_inherited_dependencies.gif b/org.eclipse.m2e.pde.ui/icons/show_inherited_dependencies.gif
new file mode 100644
index 0000000000..a326000dae
Binary files /dev/null and b/org.eclipse.m2e.pde.ui/icons/show_inherited_dependencies.gif differ
diff --git a/org.eclipse.m2e.pde.ui/plugin.xml b/org.eclipse.m2e.pde.ui/plugin.xml
new file mode 100644
index 0000000000..e63d8921a6
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/plugin.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+ Add a maven artifact (and dependencies) to your target platform
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.m2e.pde.ui/pom.xml b/org.eclipse.m2e.pde.ui/pom.xml
new file mode 100644
index 0000000000..e4fd888b79
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/pom.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+
+ org.eclipse.m2e
+ m2e-core
+ 1.16.0-SNAPSHOT
+
+
+ org.eclipse.m2e.pde.ui
+ 1.17.0-SNAPSHOT
+ eclipse-plugin
+
+ Maven PDE UI Integration
+
\ No newline at end of file
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/Activator.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/Activator.java
new file mode 100644
index 0000000000..e4763b8c80
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/Activator.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui;
+
+import org.eclipse.m2e.pde.ui.adapter.DependencyNodeAdapterFactory;
+import org.eclipse.m2e.pde.ui.adapter.MavenTargetAdapterFactory;
+import org.eclipse.m2e.pde.ui.adapter.MavenTargetBundleAdapterFactory;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ DependencyNodeAdapterFactory.LABEL_PROVIDER.dispose();
+ DependencyNodeAdapterFactory.TREE_CONTENT_PROVIDER.dispose();
+ MavenTargetAdapterFactory.LABEL_PROVIDER.dispose();
+ MavenTargetAdapterFactory.TREE_CONTENT_PROVIDER.dispose();
+ MavenTargetBundleAdapterFactory.LABEL_PROVIDER.dispose();
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/MavenTargetLocationUpdater.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/MavenTargetLocationUpdater.java
new file mode 100644
index 0000000000..871272b36c
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/MavenTargetLocationUpdater.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.m2e.pde.MavenTargetLocation;
+import org.eclipse.pde.core.target.ITargetDefinition;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.ui.target.ITargetLocationUpdater;
+
+public class MavenTargetLocationUpdater implements ITargetLocationUpdater {
+
+ @Override
+ public boolean canUpdate(ITargetDefinition target, ITargetLocation targetLocation) {
+ return targetLocation instanceof MavenTargetLocation;
+ }
+
+ @Override
+ public IStatus update(ITargetDefinition target, ITargetLocation targetLocation, IProgressMonitor monitor) {
+ MavenTargetLocation location = (MavenTargetLocation) targetLocation;
+ location.refresh();
+ return Status.OK_STATUS;
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/adapter/DependencyNodeAdapterFactory.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/adapter/DependencyNodeAdapterFactory.java
new file mode 100644
index 0000000000..9b13eeeee5
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/adapter/DependencyNodeAdapterFactory.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui.adapter;
+
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.core.runtime.IAdapterFactory;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.m2e.pde.ui.provider.DependencyNodeLabelProvider;
+import org.eclipse.m2e.pde.ui.provider.MavenTargetTreeContentProvider;
+
+public class DependencyNodeAdapterFactory implements IAdapterFactory {
+
+ public static final ITreeContentProvider TREE_CONTENT_PROVIDER = new MavenTargetTreeContentProvider();
+ public static final ILabelProvider LABEL_PROVIDER = new DependencyNodeLabelProvider();
+
+ @Override
+ public T getAdapter(Object adaptableObject, Class adapterType) {
+ if (adaptableObject instanceof DependencyNode) {
+ if (adapterType == ITreeContentProvider.class) {
+ return adapterType.cast(TREE_CONTENT_PROVIDER);
+ } else if (adapterType == ILabelProvider.class) {
+ return adapterType.cast(LABEL_PROVIDER);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Class>[] getAdapterList() {
+ return new Class>[] { ITreeContentProvider.class, ILabelProvider.class };
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/adapter/MavenTargetAdapterFactory.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/adapter/MavenTargetAdapterFactory.java
new file mode 100644
index 0000000000..fac414f8d7
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/adapter/MavenTargetAdapterFactory.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui.adapter;
+
+import org.eclipse.core.runtime.IAdapterFactory;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.m2e.pde.MavenTargetLocation;
+import org.eclipse.m2e.pde.ui.MavenTargetLocationUpdater;
+import org.eclipse.m2e.pde.ui.editor.MavenTargetLocationEditor;
+import org.eclipse.m2e.pde.ui.provider.MavenTargetLocationLabelProvider;
+import org.eclipse.m2e.pde.ui.provider.MavenTargetTreeContentProvider;
+import org.eclipse.pde.ui.target.ITargetLocationEditor;
+import org.eclipse.pde.ui.target.ITargetLocationUpdater;
+
+public class MavenTargetAdapterFactory implements IAdapterFactory {
+
+ public static final ILabelProvider LABEL_PROVIDER = new MavenTargetLocationLabelProvider();
+ public static final ITreeContentProvider TREE_CONTENT_PROVIDER = new MavenTargetTreeContentProvider();
+ private static final MavenTargetLocationEditor LOCATION_EDITOR = new MavenTargetLocationEditor();
+ private static final MavenTargetLocationUpdater LOCATION_UPDATER = new MavenTargetLocationUpdater();
+
+ @Override
+ public Class>[] getAdapterList() {
+ return new Class[] { ILabelProvider.class, ITreeContentProvider.class, ITargetLocationEditor.class,
+ ITargetLocationUpdater.class };
+ }
+
+ @Override
+ public T getAdapter(Object adaptableObject, Class adapterType) {
+ if (adaptableObject instanceof MavenTargetLocation) {
+ if (adapterType == ILabelProvider.class) {
+ return adapterType.cast(LABEL_PROVIDER);
+ } else if (adapterType == ITreeContentProvider.class) {
+ return adapterType.cast(TREE_CONTENT_PROVIDER);
+ } else if (adapterType == ITargetLocationEditor.class) {
+ return adapterType.cast(LOCATION_EDITOR);
+ } else if (adapterType == ITargetLocationUpdater.class) {
+ return adapterType.cast(LOCATION_UPDATER);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/adapter/MavenTargetBundleAdapterFactory.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/adapter/MavenTargetBundleAdapterFactory.java
new file mode 100644
index 0000000000..ef6a7afba6
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/adapter/MavenTargetBundleAdapterFactory.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui.adapter;
+
+import org.eclipse.core.runtime.IAdapterFactory;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.m2e.pde.MavenTargetBundle;
+import org.eclipse.m2e.pde.ui.provider.MavenTargetBundleLabelProvider;
+
+public class MavenTargetBundleAdapterFactory implements IAdapterFactory {
+
+ public static final ILabelProvider LABEL_PROVIDER = new MavenTargetBundleLabelProvider();
+
+ @Override
+ public T getAdapter(Object adaptableObject, Class adapterType) {
+ if (adaptableObject instanceof MavenTargetBundle) {
+ if (adapterType == ILabelProvider.class) {
+ return adapterType.cast(LABEL_PROVIDER);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Class>[] getAdapterList() {
+ return new Class>[] { ILabelProvider.class };
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/MavenTargetLocationEditor.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/MavenTargetLocationEditor.java
new file mode 100644
index 0000000000..3efb20bb6b
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/MavenTargetLocationEditor.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui.editor;
+
+import org.eclipse.jface.wizard.IWizard;
+import org.eclipse.m2e.pde.MavenTargetLocation;
+import org.eclipse.pde.core.target.ITargetDefinition;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.ui.target.ITargetLocationEditor;
+
+public class MavenTargetLocationEditor implements ITargetLocationEditor {
+
+ @Override
+ public boolean canEdit(ITargetDefinition target, ITargetLocation targetLocation) {
+ return targetLocation instanceof MavenTargetLocation;
+ }
+
+ @Override
+ public IWizard getEditWizard(ITargetDefinition target, ITargetLocation targetLocation) {
+ MavenTargetLocationWizard wizard = new MavenTargetLocationWizard((MavenTargetLocation) targetLocation);
+ wizard.setTarget(target);
+ return wizard;
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/MavenTargetLocationWizard.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/MavenTargetLocationWizard.java
new file mode 100644
index 0000000000..1fdaf075bb
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/MavenTargetLocationWizard.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui.editor;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ComboViewer;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.m2e.pde.MavenTargetLocation;
+import org.eclipse.m2e.pde.MissingMetadataMode;
+import org.eclipse.pde.core.target.ITargetDefinition;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.ui.target.ITargetLocationWizard;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CCombo;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+public class MavenTargetLocationWizard extends Wizard implements ITargetLocationWizard {
+
+ private Text artifactId;
+ private Text groupId;
+ private Text version;
+ private CCombo type;
+ private MavenTargetLocation targetLocation;
+ private CCombo scope;
+ private ComboViewer metadata;
+ private ITargetDefinition targetDefinition;
+
+ public MavenTargetLocationWizard() {
+ this(null);
+ }
+
+ public MavenTargetLocationWizard(MavenTargetLocation targetLocation) {
+ this.targetLocation = targetLocation;
+ setWindowTitle("Maven Artifact Target Entry");
+ WizardPage page = new WizardPage(
+ targetLocation == null ? "Add a new Maven dependency" : "Edit Maven Dependency") {
+
+ @Override
+ public void createControl(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NONE);
+ setControl(composite);
+ composite.setLayout(new GridLayout(2, false));
+ new Label(composite, SWT.NONE).setText("Group Id");
+ groupId = fill(new Text(composite, SWT.BORDER));
+ new Label(composite, SWT.NONE).setText("Artifact Id");
+ artifactId = fill(new Text(composite, SWT.BORDER));
+ new Label(composite, SWT.NONE).setText("Version");
+ version = fill(new Text(composite, SWT.BORDER));
+ new Label(composite, SWT.NONE).setText("Type");
+ type = new CCombo(composite, SWT.BORDER);
+ type.add("jar");
+ type.add("bundle");
+ new Label(composite, SWT.NONE).setText("Missing OSGi-Manifest");
+ metadata = new ComboViewer(composite);
+ metadata.setContentProvider(ArrayContentProvider.getInstance());
+ metadata.setLabelProvider(new ColumnLabelProvider() {
+ @Override
+ public String getText(Object element) {
+ if (element instanceof MissingMetadataMode) {
+ return ((MissingMetadataMode) element).name().toLowerCase();
+ }
+ return super.getText(element);
+ }
+
+ });
+ metadata.setInput(MissingMetadataMode.values());
+ new Label(composite, SWT.NONE).setText("Dependencies scope");
+ scope = new CCombo(composite, SWT.BORDER);
+ scope.add("");
+ scope.add("compile");
+ scope.add("test");
+ scope.add("provided");
+ if (targetLocation != null) {
+ artifactId.setText(targetLocation.getArtifactId());
+ groupId.setText(targetLocation.getGroupId());
+ version.setText(targetLocation.getVersion());
+ type.setText(targetLocation.getArtifactType());
+ scope.setText(targetLocation.getDependencyScope());
+ metadata.setSelection(new StructuredSelection(targetLocation.getMetadataMode()));
+ } else {
+ artifactId.setText("");
+ groupId.setText("");
+ version.setText("");
+ type.setText(MavenTargetLocation.DEFAULT_PACKAGE_TYPE);
+ scope.setText(MavenTargetLocation.DEFAULT_DEPENDENCY_SCOPE);
+ metadata.setSelection(new StructuredSelection(MavenTargetLocation.DEFAULT_METADATA_MODE));
+ }
+ }
+
+ private Text fill(Text text) {
+ GridData data = new GridData(GridData.FILL_HORIZONTAL);
+ data.grabExcessHorizontalSpace = true;
+ text.setLayoutData(data);
+ return text;
+ }
+ };
+ page.setImageDescriptor(ImageDescriptor.createFromURL(
+ MavenTargetLocationWizard.class.getResource("/icons/new_m2_project_wizard.gif")));
+ page.setTitle(page.getName());
+ page.setDescription("Enter the desired maven artifact to add to the target platform");
+ addPage(page);
+
+ }
+
+ @Override
+ public void setTarget(ITargetDefinition target) {
+ this.targetDefinition = target;
+ }
+
+ @Override
+ public ITargetLocation[] getLocations() {
+ return new ITargetLocation[] { targetLocation };
+ }
+
+ @Override
+ public boolean performFinish() {
+ if (targetLocation == null) {
+ targetLocation = new MavenTargetLocation(groupId.getText(), artifactId.getText(), version.getText(),
+ type.getText(), (MissingMetadataMode) metadata.getStructuredSelection().getFirstElement(),
+ scope.getText());
+ } else {
+ ITargetLocation[] locations = targetDefinition.getTargetLocations().clone();
+ for (int i = 0; i < locations.length; i++) {
+ if (locations[i] == targetLocation) {
+ locations[i] = new MavenTargetLocation(groupId.getText(), artifactId.getText(), version.getText(),
+ type.getText(), (MissingMetadataMode) metadata.getStructuredSelection().getFirstElement(),
+ scope.getText());
+ }
+
+ }
+ targetDefinition.setTargetLocations(locations);
+ }
+ return true;
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/DependencyNodeLabelProvider.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/DependencyNodeLabelProvider.java
new file mode 100644
index 0000000000..d6e302b653
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/DependencyNodeLabelProvider.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui.provider;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.m2e.pde.MavenTargetLocation;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class DependencyNodeLabelProvider implements ILabelProvider {
+ private Image inheritedImage;
+ private Image jarImage;
+ private Image errorImage;
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof DependencyNode) {
+ DependencyNode node = (DependencyNode) element;
+ Artifact artifact = node.getArtifact();
+ MavenTargetLocation location = getTargetLocation(node);
+ String baseLabel = artifact.getGroupId() + ":" + artifact.getArtifactId() + " (" + artifact.getVersion()
+ + ")";
+ if (location != null) {
+ if (location.isIgnored(artifact)) {
+ return "(ignored) " + baseLabel;
+ } else if (location.isFailed(artifact)) {
+ return "(failed) " + baseLabel;
+ }
+ }
+ return baseLabel;
+ }
+ return String.valueOf(element);
+ }
+
+ private MavenTargetLocation getTargetLocation(DependencyNode node) {
+ Object object = node.getData().get(MavenTargetLocation.DEPENDENCYNODE_PARENT);
+ if (object instanceof DependencyNode) {
+ return getTargetLocation((DependencyNode) object);
+ } else if (object instanceof MavenTargetLocation) {
+ return (MavenTargetLocation) object;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof DependencyNode) {
+ DependencyNode node = (DependencyNode) element;
+ MavenTargetLocation location = getTargetLocation(node);
+ Display current = Display.getCurrent();
+ if (location != null) {
+ if (location.isIgnored(node.getArtifact())) {
+ if (jarImage == null && current != null) {
+ jarImage = new Image(current, DependencyNodeLabelProvider.class
+ .getResourceAsStream("/icons/jar_obj.gif"));
+ }
+ return jarImage;
+ } else if (location.isFailed(node.getArtifact())) {
+ if (errorImage == null && current != null) {
+ errorImage = new Image(current,
+ DependencyNodeLabelProvider.class.getResourceAsStream("/icons/error_st_obj.gif"));
+ }
+ return errorImage;
+ }
+ }
+ if (inheritedImage == null && current != null) {
+ inheritedImage = new Image(current, DependencyNodeLabelProvider.class
+ .getResourceAsStream("/icons/show_inherited_dependencies.gif"));
+ }
+ return inheritedImage;
+ }
+ return null;
+ }
+
+ @Override
+ public void addListener(ILabelProviderListener listener) {
+
+ }
+
+ @Override
+ public void dispose() {
+ if (inheritedImage != null) {
+ inheritedImage.dispose();
+ }
+ if (jarImage != null) {
+ jarImage.dispose();
+ }
+ if (errorImage != null) {
+ errorImage.dispose();
+ }
+ }
+
+ @Override
+ public boolean isLabelProperty(Object element, String property) {
+ return false;
+ }
+
+ @Override
+ public void removeListener(ILabelProviderListener listener) {
+
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/MavenTargetBundleLabelProvider.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/MavenTargetBundleLabelProvider.java
new file mode 100644
index 0000000000..b1ef8d9b08
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/MavenTargetBundleLabelProvider.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui.provider;
+
+import org.eclipse.m2e.pde.MavenTargetBundle;
+import org.eclipse.m2e.pde.ui.adapter.MavenTargetAdapterFactory;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+@SuppressWarnings("restriction")
+public class MavenTargetBundleLabelProvider
+ extends org.eclipse.pde.internal.ui.shared.target.StyledBundleLabelProvider {
+
+ private Image image;
+
+ public MavenTargetBundleLabelProvider() {
+ super(true, false);
+ }
+
+ @Override
+ public org.eclipse.swt.graphics.Image getImage(Object element) {
+ if (element instanceof MavenTargetBundle) {
+ if (((MavenTargetBundle) element).isWrapped()) {
+ Display current = Display.getCurrent();
+ if (image == null && current != null) {
+ image = new Image(current,
+ MavenTargetAdapterFactory.class.getResourceAsStream("/icons/jar_obj.gif"));
+ }
+ return image;
+ }
+ }
+ return super.getImage(element);
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ if (image != null) {
+ image.dispose();
+ }
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/MavenTargetLocationLabelProvider.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/MavenTargetLocationLabelProvider.java
new file mode 100644
index 0000000000..4ed922863b
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/MavenTargetLocationLabelProvider.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui.provider;
+
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.m2e.pde.MavenTargetLocation;
+import org.eclipse.m2e.pde.ui.adapter.MavenTargetAdapterFactory;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+public class MavenTargetLocationLabelProvider implements ILabelProvider {
+ private Image image;
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof MavenTargetLocation) {
+ MavenTargetLocation location = (MavenTargetLocation) element;
+ return location.getGroupId() + ":" + location.getArtifactId() + " (" + location.getVersion() + ")";
+ }
+ return String.valueOf(element);
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ Display current = Display.getCurrent();
+ if (image == null && current != null) {
+ image = new Image(current, MavenTargetAdapterFactory.class.getResourceAsStream("/icons/m2.gif"));
+ }
+ return image;
+ }
+
+ @Override
+ public void addListener(ILabelProviderListener listener) {
+
+ }
+
+ @Override
+ public void dispose() {
+ image.dispose();
+
+ }
+
+ @Override
+ public boolean isLabelProperty(Object element, String property) {
+ return false;
+ }
+
+ @Override
+ public void removeListener(ILabelProviderListener listener) {
+
+ }
+
+}
diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/MavenTargetTreeContentProvider.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/MavenTargetTreeContentProvider.java
new file mode 100644
index 0000000000..9b69c11e65
--- /dev/null
+++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/provider/MavenTargetTreeContentProvider.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde.ui.provider;
+
+import java.util.List;
+
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.m2e.pde.MavenTargetLocation;
+
+public class MavenTargetTreeContentProvider implements ITreeContentProvider {
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ return getChildren(inputElement);
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof MavenTargetLocation) {
+ MavenTargetLocation location = (MavenTargetLocation) parentElement;
+ List nodes = location.getDependencyNodes();
+ if (nodes != null) {
+ for (DependencyNode dependencyNode : nodes) {
+ if (dependencyNode.getData().containsKey(MavenTargetLocation.DEPENDENCYNODE_IS_ROOT)) {
+ dependencyNode.setData(MavenTargetLocation.DEPENDENCYNODE_PARENT, parentElement);
+ return getChildren(dependencyNode);
+ }
+ }
+ }
+ } else if (parentElement instanceof DependencyNode) {
+ DependencyNode[] dependencyNodes = ((DependencyNode) parentElement).getChildren()
+ .toArray(new DependencyNode[0]);
+ for (DependencyNode dependencyNode : dependencyNodes) {
+ dependencyNode.setData(MavenTargetLocation.DEPENDENCYNODE_PARENT, parentElement);
+ }
+ return dependencyNodes;
+ }
+ return null;
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ if (element instanceof DependencyNode) {
+ DependencyNode dependencyNode = (DependencyNode) element;
+ Object parent = dependencyNode.getData().get(MavenTargetLocation.DEPENDENCYNODE_PARENT);
+ if (parent instanceof DependencyNode) {
+ DependencyNode dp = (DependencyNode) parent;
+ if (dp.getData().containsKey(MavenTargetLocation.DEPENDENCYNODE_IS_ROOT)) {
+ return getParent(dp);
+ }
+ }
+ return parent;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ if (element instanceof MavenTargetLocation) {
+ MavenTargetLocation location = (MavenTargetLocation) element;
+ List dependencyNodes = location.getDependencyNodes();
+ return dependencyNodes != null && !dependencyNodes.isEmpty();
+ } else if (element instanceof DependencyNode) {
+ DependencyNode node = (DependencyNode) element;
+ return !node.getChildren().isEmpty();
+ }
+ return false;
+ }
+
+}
diff --git a/org.eclipse.m2e.pde/META-INF/MANIFEST.MF b/org.eclipse.m2e.pde/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..b00df12c30
--- /dev/null
+++ b/org.eclipse.m2e.pde/META-INF/MANIFEST.MF
@@ -0,0 +1,14 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: M2E PDE Integration
+Bundle-SymbolicName: org.eclipse.m2e.pde;singleton:=true
+Bundle-Version: 1.17.0.qualifier
+Automatic-Module-Name: org.eclipse.m2e.pde
+Bundle-RequiredExecutionEnvironment: JavaSE-11
+Require-Bundle: org.eclipse.core.runtime;bundle-version="3.19.0",
+ org.eclipse.pde.ui;bundle-version="3.12.0",
+ org.eclipse.equinox.frameworkadmin;bundle-version="2.1.400",
+ biz.aQute.bndlib;bundle-version="5.1.2",
+ org.eclipse.m2e.maven.runtime,
+ org.eclipse.m2e.core
+Export-Package: org.eclipse.m2e.pde
diff --git a/org.eclipse.m2e.pde/build.properties b/org.eclipse.m2e.pde/build.properties
new file mode 100644
index 0000000000..e9863e281e
--- /dev/null
+++ b/org.eclipse.m2e.pde/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml
diff --git a/org.eclipse.m2e.pde/plugin.xml b/org.eclipse.m2e.pde/plugin.xml
new file mode 100644
index 0000000000..c664d3ec46
--- /dev/null
+++ b/org.eclipse.m2e.pde/plugin.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.m2e.pde/pom.xml b/org.eclipse.m2e.pde/pom.xml
new file mode 100644
index 0000000000..2f11cb1eb2
--- /dev/null
+++ b/org.eclipse.m2e.pde/pom.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+
+ org.eclipse.m2e
+ m2e-core
+ 1.16.0-SNAPSHOT
+
+
+ org.eclipse.m2e.pde
+ 1.17.0-SNAPSHOT
+ eclipse-plugin
+
+ Maven PDE Integration
+
\ No newline at end of file
diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetBundle.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetBundle.java
new file mode 100644
index 0000000000..3732608a34
--- /dev/null
+++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetBundle.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde;
+
+import java.io.File;
+import java.util.jar.Manifest;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.equinox.frameworkadmin.BundleInfo;
+import org.eclipse.pde.core.target.TargetBundle;
+
+import aQute.bnd.osgi.Analyzer;
+import aQute.bnd.osgi.Jar;
+
+public class MavenTargetBundle extends TargetBundle {
+
+ private TargetBundle bundle;
+ private IStatus status;
+ private BundleInfo bundleInfo;
+ private boolean isWrapped;
+
+ @Override
+ public BundleInfo getBundleInfo() {
+ if (bundle == null) {
+ return bundleInfo;
+ }
+ return bundle.getBundleInfo();
+ }
+
+ @Override
+ public boolean isSourceBundle() {
+ if (bundle == null) {
+ return false;
+ }
+ return bundle.isSourceBundle();
+ }
+
+ @Override
+ public BundleInfo getSourceTarget() {
+ if (bundle == null) {
+ return null;
+ }
+ return bundle.getSourceTarget();
+ }
+
+ @Override
+ public boolean isFragment() {
+ if (bundle == null) {
+ return false;
+ }
+ return bundle.isFragment();
+ }
+
+ @Override
+ public String getSourcePath() {
+ if (bundle == null) {
+ return null;
+ }
+ return bundle.getSourcePath();
+ }
+
+ public MavenTargetBundle(Artifact artifact, MissingMetadataMode metadataMode) {
+ File file = artifact.getFile();
+ this.bundleInfo = new BundleInfo(artifact.getGroupId() + "." + artifact.getArtifactId(), artifact.getVersion(),
+ file != null ? file.toURI() : null, -1, false);
+ try {
+ bundle = new TargetBundle(file);
+ } catch (Exception ex) {
+ if (metadataMode == MissingMetadataMode.ERROR) {
+ status = new Status(Status.ERROR, MavenTargetBundle.class.getPackage().getName(),
+ artifact + " is not a bundle", ex);
+ } else if (metadataMode == MissingMetadataMode.GENERATE) {
+ try {
+ bundle = getWrappedArtifact(artifact, bundleInfo);
+ isWrapped = true;
+ } catch (Exception e) {
+ // not possible then
+ String message = artifact + " is not a bundle and cannot be automatically bundled as such ";
+ if (e.getMessage() != null) {
+ message += " (" + e.getMessage() + ")";
+ }
+ status = new Status(Status.ERROR, MavenTargetBundle.class.getPackage().getName(), message, e);
+ }
+ } else {
+ status = Status.CANCEL_STATUS;
+ }
+ }
+ }
+
+ public static TargetBundle getWrappedArtifact(Artifact artifact, BundleInfo bundleInfo) throws Exception {
+ synchronized (artifact) {
+ File file = artifact.getFile();
+ String name = file.getName();
+ File wrappedFile = new File(file.getParentFile(), name + ".wrapped");
+ if (!wrappedFile.exists() || file.lastModified() > wrappedFile.lastModified()) {
+ try (Jar jar = new Jar(file)) {
+ Manifest originalManifest = jar.getManifest();
+ try (Analyzer analyzer = new Analyzer();) {
+ analyzer.setJar(jar);
+ if (originalManifest != null) {
+ analyzer.mergeManifest(originalManifest);
+ }
+ analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*;resolution:=optional");
+ analyzer.setProperty(Analyzer.EXPORT_PACKAGE, "*;-noimport:=true");
+ analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, createSymbolicName(artifact));
+ analyzer.setProperty(Analyzer.BUNDLE_NAME, "Derived from " + artifact.getGroupId() + ":"
+ + artifact.getArtifactId() + ":" + artifact.getVersion());
+ analyzer.setBundleVersion(createBundleVersion(artifact));
+ jar.setManifest(analyzer.calcManifest());
+ jar.write(wrappedFile);
+ }
+ }
+ return new TargetBundle(wrappedFile);
+ }
+ try {
+ return new TargetBundle(wrappedFile);
+ } catch (Exception e) {
+ // cached file seems invalid/stale...
+ file.delete();
+ return getWrappedArtifact(artifact, bundleInfo);
+ }
+ }
+ }
+
+ public static String createBundleVersion(Artifact artifact) {
+ String version = artifact.getVersion();
+ if (version == null || version.isEmpty()) {
+ return "0";
+ }
+ return version.replaceAll("[^a-zA-Z0-9\\.]", ".").replaceAll("\\.\\.+", ".");
+ }
+
+ public static String createSymbolicName(Artifact artifact) {
+
+ return "wrapped." + artifact.getGroupId() + "." + artifact.getArtifactId();
+ }
+
+ public boolean isWrapped() {
+ return isWrapped;
+ }
+
+ @Override
+ public IStatus getStatus() {
+ if (bundle == null) {
+ if (status == null) {
+ return Status.OK_STATUS;
+ }
+ return status;
+ }
+ return bundle.getStatus();
+ }
+
+ @Override
+ public int hashCode() {
+ return getBundleInfo().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MavenTargetBundle) {
+ MavenTargetBundle other = (MavenTargetBundle) obj;
+ return getBundleInfo().equals(other.getBundleInfo());
+ }
+ return false;
+ }
+}
diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocation.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocation.java
new file mode 100644
index 0000000000..ce60a92225
--- /dev/null
+++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocation.java
@@ -0,0 +1,287 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.eclipse.aether.RepositoryException;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.m2e.core.MavenPlugin;
+import org.eclipse.m2e.core.embedder.ICallable;
+import org.eclipse.m2e.core.embedder.IMaven;
+import org.eclipse.m2e.core.embedder.IMavenExecutionContext;
+import org.eclipse.m2e.core.internal.MavenPluginActivator;
+import org.eclipse.pde.core.target.ITargetDefinition;
+import org.eclipse.pde.core.target.TargetBundle;
+import org.eclipse.pde.core.target.TargetFeature;
+import org.eclipse.pde.internal.core.target.AbstractBundleContainer;
+
+@SuppressWarnings("restriction")
+public class MavenTargetLocation extends AbstractBundleContainer {
+
+ public static final String ELEMENT_TYPE = "type";
+ public static final String ELEMENT_VERSION = "version";
+ public static final String ELEMENT_ARTIFACT_ID = "artifactId";
+ public static final String ELEMENT_GROUP_ID = "groupId";
+ public static final String ATTRIBUTE_DEPENDENCY_SCOPE = "includeDependencyScope";
+ public static final String ATTRIBUTE_MISSING_META_DATA = "missingManfiest";
+ public static final String DEFAULT_DEPENDENCY_SCOPE = "";
+ public static final MissingMetadataMode DEFAULT_METADATA_MODE = MissingMetadataMode.GENERATE;
+ public static final String DEFAULT_PACKAGE_TYPE = "jar";
+ public static final String DEPENDENCYNODE_IS_ROOT = "dependencynode.root";
+ public static final String DEPENDENCYNODE_PARENT = "dependencynode.parent";
+
+ private final String artifactId;
+ private final String groupId;
+ private final String version;
+ private final String artifactType;
+ private final String dependencyScope;
+ private final MissingMetadataMode metadataMode;
+ private List targetBundles;
+ private List dependencyNodes;
+ private Set ignoredArtifacts = new HashSet<>();
+
+ private Set failedArtifacts = new HashSet<>();
+
+ public MavenTargetLocation(String groupId, String artifactId, String version, String artifactType,
+ MissingMetadataMode metadataMode, String dependencyScope) {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ this.version = version;
+ this.artifactType = artifactType;
+ this.metadataMode = metadataMode;
+ this.dependencyScope = dependencyScope;
+ }
+
+ @Override
+ protected TargetBundle[] resolveBundles(ITargetDefinition definition, IProgressMonitor monitor)
+ throws CoreException {
+ if (targetBundles == null) {
+ ignoredArtifacts.clear();
+ targetBundles = new ArrayList<>();
+ IMaven maven = MavenPlugin.getMaven();
+ List repositories = maven.getArtifactRepositories();
+ Artifact artifact = RepositoryUtils.toArtifact(maven.resolve(getGroupId(), getArtifactId(), getVersion(),
+ getArtifactType(), null, repositories, monitor));
+ if (artifact != null) {
+ if (dependencyScope != null && !dependencyScope.isBlank()) {
+ IMavenExecutionContext context = maven.createExecutionContext();
+ PreorderNodeListGenerator dependecies = context.execute(new ICallable() {
+
+ @Override
+ public PreorderNodeListGenerator call(IMavenExecutionContext context, IProgressMonitor monitor)
+ throws CoreException {
+ try {
+ CollectRequest collectRequest = new CollectRequest();
+ collectRequest.setRoot(new Dependency(artifact, dependencyScope));
+ collectRequest.setRepositories(RepositoryUtils.toRepos(repositories));
+
+ RepositorySystem repoSystem = MavenPluginActivator.getDefault().getRepositorySystem();
+ DependencyNode node = repoSystem
+ .collectDependencies(context.getRepositorySession(), collectRequest).getRoot();
+ node.setData(DEPENDENCYNODE_IS_ROOT, true);
+ node.setData(DEPENDENCYNODE_PARENT, MavenTargetLocation.this);
+ DependencyRequest dependencyRequest = new DependencyRequest();
+ dependencyRequest.setRoot(node);
+ repoSystem.resolveDependencies(context.getRepositorySession(), dependencyRequest);
+ PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
+ node.accept(nlg);
+ return nlg;
+ } catch (RepositoryException e) {
+ e.printStackTrace();
+ throw new CoreException(
+ new Status(IStatus.ERROR, MavenTargetLocation.class.getPackage().getName(),
+ "Resolving dependencies failed", e));
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ throw new CoreException(new Status(IStatus.ERROR,
+ MavenTargetLocation.class.getPackage().getName(), "Internal error", e));
+ }
+ }
+ }, monitor);
+
+ for (Artifact a : dependecies.getArtifacts(true)) {
+ addBundleForArtifact(a);
+ }
+ dependencyNodes = dependecies.getNodes();
+ } else {
+ addBundleForArtifact(artifact);
+ }
+ }
+ }
+ return targetBundles.toArray(new TargetBundle[0]);
+ }
+
+ private void addBundleForArtifact(Artifact artifact) {
+ TargetBundle bundle = createTargetBundle(artifact);
+ IStatus status = bundle.getStatus();
+ if (status.isOK()) {
+ targetBundles.add(bundle);
+ } else if (status.matches(IStatus.CANCEL)) {
+ ignoredArtifacts.add(artifact);
+ } else {
+ failedArtifacts.add(artifact);
+ // failed ones must be added to the target as well to fail resolution of the TP
+ targetBundles.add(bundle);
+ }
+ }
+
+ public int getDependencyCount() {
+ if (targetBundles == null) {
+ return -1;
+ }
+ return targetBundles.size() - 1;
+ }
+
+ private TargetBundle createTargetBundle(Artifact artifact) {
+ return new MavenTargetBundle(artifact, metadataMode);
+ }
+
+ public List getDependencyNodes() {
+ return dependencyNodes;
+ }
+
+ @Override
+ protected TargetFeature[] resolveFeatures(ITargetDefinition definition, IProgressMonitor monitor)
+ throws CoreException {
+ // XXX it would be possible to deploy features as maven artifacts, are there any
+ // examples?
+ return new TargetFeature[] {};
+ }
+
+ @Override
+ public String getType() {
+ return "Maven";
+ }
+
+ @Override
+ public String getLocation(boolean resolve) throws CoreException {
+ return System.getProperty("java.io.tmpdir");
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(artifactId, artifactType, dependencyNodes, dependencyScope, failedArtifacts, groupId,
+ ignoredArtifacts, metadataMode, targetBundles, version);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ MavenTargetLocation other = (MavenTargetLocation) obj;
+ return Objects.equals(artifactId, other.artifactId) && Objects.equals(artifactType, other.artifactType)
+ && Objects.equals(dependencyNodes, other.dependencyNodes)
+ && Objects.equals(dependencyScope, other.dependencyScope)
+ && Objects.equals(failedArtifacts, other.failedArtifacts) && Objects.equals(groupId, other.groupId)
+ && Objects.equals(ignoredArtifacts, other.ignoredArtifacts) && metadataMode == other.metadataMode
+ && Objects.equals(targetBundles, other.targetBundles) && Objects.equals(version, other.version);
+ }
+
+ @Override
+ public String serialize() {
+ StringBuilder xml = new StringBuilder();
+ xml.append("");
+ xml.append("<" + ELEMENT_GROUP_ID + ">");
+ xml.append(groupId);
+ xml.append("" + ELEMENT_GROUP_ID + ">");
+ xml.append("<" + ELEMENT_ARTIFACT_ID + ">");
+ xml.append(artifactId);
+ xml.append("" + ELEMENT_ARTIFACT_ID + ">");
+ xml.append("<" + ELEMENT_VERSION + ">");
+ xml.append(version);
+ xml.append("" + ELEMENT_VERSION + ">");
+ xml.append("<" + ELEMENT_TYPE + ">");
+ xml.append(artifactType);
+ xml.append("" + ELEMENT_TYPE + ">");
+ xml.append("");
+ return xml.toString();
+ }
+
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public MissingMetadataMode getMetadataMode() {
+ if (metadataMode == null) {
+ return DEFAULT_METADATA_MODE;
+ }
+ return metadataMode;
+ }
+
+ public void refresh() {
+ dependencyNodes = null;
+ targetBundles = null;
+ }
+
+ public String getArtifactType() {
+ if (artifactType != null && !artifactType.trim().isEmpty()) {
+ return artifactType;
+ }
+ return DEFAULT_PACKAGE_TYPE;
+ }
+
+ public String getDependencyScope() {
+ if (dependencyScope != null && !dependencyScope.trim().isEmpty()) {
+ return dependencyScope;
+ }
+ return DEFAULT_DEPENDENCY_SCOPE;
+ }
+
+ public boolean isIgnored(Artifact artifact) {
+ return ignoredArtifacts.contains(artifact);
+ }
+
+ public boolean isFailed(Artifact artifact) {
+ return failedArtifacts.contains(artifact);
+ }
+}
diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocationFactory.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocationFactory.java
new file mode 100644
index 0000000000..bf03e9498e
--- /dev/null
+++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocationFactory.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.core.target.ITargetLocationFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class MavenTargetLocationFactory implements ITargetLocationFactory {
+
+ @Override
+ public ITargetLocation getTargetLocation(String type, String serializedXML) throws CoreException {
+ try {
+ DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ Document document = docBuilder
+ .parse(new ByteArrayInputStream(serializedXML.getBytes(StandardCharsets.UTF_8)));
+ Element location = document.getDocumentElement();
+ MissingMetadataMode mode = MissingMetadataMode
+ .valueOf(location.getAttribute(MavenTargetLocation.ATTRIBUTE_MISSING_META_DATA).toUpperCase());
+ String dependencyScope = location.getAttribute(MavenTargetLocation.ATTRIBUTE_DEPENDENCY_SCOPE);
+ String artifactId = getText(MavenTargetLocation.ELEMENT_ARTIFACT_ID, location);
+ String groupId = getText(MavenTargetLocation.ELEMENT_GROUP_ID, location);
+ String version = getText(MavenTargetLocation.ELEMENT_VERSION, location);
+ String artifactType = getText(MavenTargetLocation.ELEMENT_TYPE, location);
+ return new MavenTargetLocation(groupId, artifactId, version, artifactType, mode, dependencyScope);
+ } catch (Exception e) {
+ throw new CoreException(new Status(IStatus.ERROR, MavenTargetLocationFactory.class.getPackage().getName(),
+ e.getMessage(), e));
+ }
+
+ }
+
+ private String getText(String tagName, Element location) {
+ NodeList nodeList = location.getElementsByTagName(tagName);
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ String textContent = nodeList.item(i).getTextContent();
+ if (textContent != null) {
+ return textContent;
+ }
+ }
+ return "";
+ }
+
+}
diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MissingMetadataMode.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MissingMetadataMode.java
new file mode 100644
index 0000000000..2f27d393a8
--- /dev/null
+++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MissingMetadataMode.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Christoph Läubrich
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.m2e.pde;
+
+public enum MissingMetadataMode {
+ IGNORE, ERROR, GENERATE;
+}
diff --git a/org.eclipse.m2e.site/category.xml b/org.eclipse.m2e.site/category.xml
index e7e7651372..f032b94a9f 100644
--- a/org.eclipse.m2e.site/category.xml
+++ b/org.eclipse.m2e.site/category.xml
@@ -15,5 +15,8 @@
+
+
+
diff --git a/org.eclipse.m2e.site/org.eclipse.m2e.site.product b/org.eclipse.m2e.site/org.eclipse.m2e.site.product
index 7df88ed61f..4d3d00d55a 100644
--- a/org.eclipse.m2e.site/org.eclipse.m2e.site.product
+++ b/org.eclipse.m2e.site/org.eclipse.m2e.site.product
@@ -4,10 +4,11 @@
-
-
-
-
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index d1fe6ada89..32a851b4ab 100644
--- a/pom.xml
+++ b/pom.xml
@@ -93,6 +93,11 @@
p2
https://download.eclipse.org/wildwebdeveloper/releases/latest/
+
+ BND
+ p2
+ https://bndtools.jfrog.io/bndtools/update-latest
+
org.eclipse.m2e.tests.common
@@ -141,6 +148,7 @@
org.eclipse.m2e.sse.ui.feature
org.eclipse.m2e.sdk.feature
org.eclipse.m2e.logback.feature
+ org.eclipse.m2e.pde.feature
org.eclipse.m2e.site