From 89a9b39e23e430b43a488c7ca74b2b365d27a72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Wed, 8 Dec 2021 18:22:42 +0100 Subject: [PATCH] #280 rebased MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Christoph Läubrich --- .gitignore | 3 +- org.eclipse.m2e.pde.ui/META-INF/MANIFEST.MF | 2 +- .../ui/editor/AbstractFeatureSpecPage.java | 171 ++++++++++++++++ .../m2e/pde/ui/editor/FeatureSpecPage.java | 166 ++++++++++++++++ .../ui/editor/MavenTargetLocationWizard.java | 107 +++++++++- .../eclipse/m2e/pde/ui/editor/Messages.java | 3 + .../m2e/pde/ui/editor/PluginListPage.java | 187 ++++++++++++++++++ .../m2e/pde/ui/editor/messages.properties | 3 + .../MavenTargetBundleLabelProvider.java | 16 +- .../MavenTargetLocationLabelProvider.java | 2 +- .../MavenTargetTreeContentProvider.java | 7 +- org.eclipse.m2e.pde/META-INF/MANIFEST.MF | 6 +- .../org/eclipse/m2e/pde/DomXmlFeature.java | 61 ++++++ .../eclipse/m2e/pde/MavenFeaturePlugin.java | 39 ++++ .../eclipse/m2e/pde/MavenPomFeatureModel.java | 169 ++++++++++++++++ .../eclipse/m2e/pde/MavenTargetBundle.java | 24 +-- .../m2e/pde/MavenTargetDependencyFilter.java | 67 +++++++ .../eclipse/m2e/pde/MavenTargetFeature.java | 25 +++ .../eclipse/m2e/pde/MavenTargetLocation.java | 158 +++++++++++---- .../m2e/pde/MavenTargetLocationFactory.java | 8 +- .../org/eclipse/m2e/pde/TargetBundles.java | 60 ++++++ .../eclipse/m2e/pde/TemplateFeatureModel.java | 90 +++++++++ target-platform/target-platform.target | 2 +- 23 files changed, 1287 insertions(+), 89 deletions(-) create mode 100644 org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/AbstractFeatureSpecPage.java create mode 100644 org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/FeatureSpecPage.java create mode 100644 org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/PluginListPage.java create mode 100644 org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/DomXmlFeature.java create mode 100644 org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenFeaturePlugin.java create mode 100644 org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenPomFeatureModel.java create mode 100644 org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetDependencyFilter.java create mode 100644 org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetFeature.java create mode 100644 org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/TemplateFeatureModel.java diff --git a/.gitignore b/.gitignore index 0c2e1b83c0..09389cf7f4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ target/ bin/ src-gen/ .DS_Store -.polyglot* \ No newline at end of file +.polyglot* +indexer-jars/ \ No newline at end of file diff --git a/org.eclipse.m2e.pde.ui/META-INF/MANIFEST.MF b/org.eclipse.m2e.pde.ui/META-INF/MANIFEST.MF index 7e7c4f9444..ae99b31348 100644 --- a/org.eclipse.m2e.pde.ui/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.pde.ui/META-INF/MANIFEST.MF @@ -13,7 +13,7 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.19.0", org.eclipse.m2e.maven.runtime, org.eclipse.m2e.core, org.eclipse.m2e.pde, - org.eclipse.pde;bundle-version="3.13.1200", + org.eclipse.pde;bundle-version="3.13.300", org.eclipse.core.databinding;bundle-version="1.10.100", org.eclipse.core.databinding.observable;bundle-version="1.10.0", org.eclipse.jface.databinding;bundle-version="1.12.200", diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/AbstractFeatureSpecPage.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/AbstractFeatureSpecPage.java new file mode 100644 index 0000000000..d3347b0679 --- /dev/null +++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/AbstractFeatureSpecPage.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2000, 2021 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Christoph Läubrich - adjust for m2e-pde usage + *******************************************************************************/ + +package org.eclipse.m2e.pde.ui.editor; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.pde.internal.core.util.IdUtil; +import org.eclipse.pde.internal.core.util.VersionUtil; +import org.eclipse.pde.internal.ui.PDEUIMessages; +import org.eclipse.pde.internal.ui.wizards.feature.FeatureData; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; + +//derived from org.eclipse.pde.internal.ui.wizards.feature.AbstractFeatureSpecPage +@SuppressWarnings("restriction") +public abstract class AbstractFeatureSpecPage extends WizardPage { + + protected Text fFeatureNameText; + protected Text fFeatureVersionText; + protected Text fLibraryText; + protected String fInitialId; + protected String fInitialName; + + public AbstractFeatureSpecPage() { + super("specPage"); //$NON-NLS-1$ + } + + @Override + public void createControl(Composite parent) { + Composite comp = new Composite(parent, SWT.NONE); + comp.setLayout(GridLayoutFactory.fillDefaults().create()); + setControl(comp); + createContents(comp); + + initialize(); + attachListeners(); + + Dialog.applyDialogFont(comp); + PlatformUI.getWorkbench().getHelpSystem().setHelp(comp, getHelpId()); + setPageComplete(validatePage()); + } + + protected abstract void createContents(Composite container); + + protected abstract void initialize(); + + protected abstract void attachListeners(ModifyListener listener); + + protected abstract String getHelpId(); + + protected abstract void saveSettings(IDialogSettings settings); + + protected void createCommonInput(Composite common) { + Label label = new Label(common, SWT.NULL); + label.setText(PDEUIMessages.NewFeatureWizard_SpecPage_name); + fFeatureNameText = new Text(common, SWT.BORDER); + fFeatureNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + label = new Label(common, SWT.NULL); + label.setText(PDEUIMessages.NewFeatureWizard_SpecPage_version); + fFeatureVersionText = new Text(common, SWT.BORDER); + fFeatureVersionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + } + + protected void createInstallHandlerText(Composite parent) { + Label libraryLabel = new Label(parent, SWT.NULL); + libraryLabel.setText(PDEUIMessages.NewFeatureWizard_SpecPage_library); + fLibraryText = new Text(parent, SWT.SINGLE | SWT.BORDER); + fLibraryText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + } + + protected boolean validatePage() { + if (!setValidationMessage(verifyIdRules())) + return false; + if (!setValidationMessage(verifyVersion())) + return false; + if (!setValidationMessage(validateContent())) + return false; + + setPageComplete(true); + setErrorMessage(null); + return true; + } + + private boolean setValidationMessage(String message) { + if (message == null) + return true; + setPageComplete(false); + setErrorMessage(message); + return false; + } + + protected abstract String validateContent(); + + public String getInitialName() { + return fInitialName; + } + + public void setInitialName(String initialName) { + fInitialName = initialName; + } + + public void setInitialId(String initialId) { + fInitialId = initialId; + } + + public String getInitialId() { + return fInitialId; + } + + protected String verifyVersion() { + String value = fFeatureVersionText.getText(); + if (VersionUtil.validateVersion(value).getSeverity() != IStatus.OK) + return PDEUIMessages.NewFeatureWizard_SpecPage_versionFormat; + return null; + } + + protected abstract String getFeatureId(); + + protected String verifyIdRules() { + String id = getFeatureId(); + if (id == null || id.length() == 0) + return PDEUIMessages.NewFeatureWizard_SpecPage_missing; + if (!IdUtil.isValidCompositeID(id)) { + return PDEUIMessages.NewFeatureWizard_SpecPage_invalidId; + } + return null; + } + + protected String getInstallHandlerLibrary() { + String library = fLibraryText.getText(); + if (library == null || library.length() == 0) + return null; + if (!library.endsWith(".jar") && !library.endsWith("/") && !library.equals(".")) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + library += "/"; //$NON-NLS-1$ + return library; + } + + private void attachListeners() { + ModifyListener listener = e -> { + setPageComplete(validatePage()); + }; + attachListeners(listener); + fFeatureNameText.addModifyListener(listener); + fFeatureVersionText.addModifyListener(listener); + fLibraryText.addModifyListener(listener); + } + + public abstract FeatureData getFeatureData(); +} diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/FeatureSpecPage.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/FeatureSpecPage.java new file mode 100644 index 0000000000..dc1b76f3a1 --- /dev/null +++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/FeatureSpecPage.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2000, 2021 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Christoph Läubrich - adjust for m2e-pde usage + *******************************************************************************/ + +package org.eclipse.m2e.pde.ui.editor; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.m2e.pde.MavenTargetLocation; +import org.eclipse.pde.internal.core.ifeature.IFeature; +import org.eclipse.pde.internal.core.ifeature.IFeatureInfo; +import org.eclipse.pde.internal.core.ifeature.IFeatureInstallHandler; +import org.eclipse.pde.internal.core.ifeature.IFeatureModel; +import org.eclipse.pde.internal.ui.IHelpContextIds; +import org.eclipse.pde.internal.ui.PDEUIMessages; +import org.eclipse.pde.internal.ui.wizards.BundleProviderHistoryUtil; +import org.eclipse.pde.internal.ui.wizards.feature.FeatureData; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +// derived from org.eclipse.pde.internal.ui.wizards.feature.FeatureSpecPage +public class FeatureSpecPage extends AbstractFeatureSpecPage { + + private Combo fFeatureProviderCombo; + private Text fFeatureIdText; + private MavenTargetLocation targetLocation; + + public FeatureSpecPage(MavenTargetLocation targetLocation) { + super(); + this.targetLocation = targetLocation; + setTitle(PDEUIMessages.NewFeatureWizard_SpecPage_title); + setDescription(PDEUIMessages.NewFeatureWizard_SpecPage_desc); + } + + @Override + protected void initialize() { + fFeatureVersionText.setText("1.0.0.qualifier"); //$NON-NLS-1$ + setMessage(PDEUIMessages.NewFeatureWizard_MainPage_desc); + } + + @Override + public FeatureData getFeatureData() { + FeatureData data = new FeatureData(); + data.id = fFeatureIdText.getText(); + data.version = fFeatureVersionText.getText(); + data.provider = fFeatureProviderCombo.getText(); + data.name = fFeatureNameText.getText(); + data.library = getInstallHandlerLibrary(); + return data; + } + + @Override + protected String validateContent() { + setMessage(null); + return null; + } + + @Override + protected String getHelpId() { + return IHelpContextIds.NEW_FEATURE_DATA; + } + + @Override + protected void createContents(Composite container) { + Composite group = new Composite(container, SWT.NULL); + group.setLayout(new GridLayout(2, false)); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.verticalIndent = 10; + group.setLayoutData(gd); + + Label label = new Label(group, SWT.NULL); + label.setText(PDEUIMessages.NewFeatureWizard_SpecPage_id); + fFeatureIdText = new Text(group, SWT.BORDER); + fFeatureIdText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + createCommonInput(group); + + label = new Label(group, SWT.NULL); + label.setText(PDEUIMessages.NewFeatureWizard_SpecPage_provider); + fFeatureProviderCombo = new Combo(group, SWT.BORDER | SWT.DROP_DOWN); + fFeatureProviderCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + BundleProviderHistoryUtil.loadHistory(fFeatureProviderCombo, getDialogSettings()); + + createInstallHandlerText(group); + if (targetLocation != null) { + IFeature template = targetLocation.getFeatureTemplate(); + if (template != null) { + fFeatureIdText.setText(template.getId()); + fFeatureNameText.setText(template.getLabel()); + fFeatureVersionText.setText(template.getVersion()); + fFeatureProviderCombo.setText(template.getProviderName()); + IFeatureInstallHandler handler = template.getInstallHandler(); + if (handler != null) { + fLibraryText.setText(handler.getHandlerName()); + } + } + } + } + + @Override + protected void attachListeners(ModifyListener listener) { + fFeatureProviderCombo.addModifyListener(listener); + fFeatureIdText.addModifyListener(listener); + } + + @Override + protected String getFeatureId() { + return fFeatureIdText.getText(); + } + + // derived from + // org.eclipse.pde.internal.ui.wizards.feature.AbstractCreateFeatureOperation.createFeature() + @SuppressWarnings("restriction") + public void update(IFeatureModel model, boolean createFeatureInfos) throws CoreException { + FeatureData featureData = getFeatureData(); + IFeature feature = model.getFeature(); + feature.setLabel(featureData.name); + feature.setId(featureData.id); + feature.setVersion(featureData.version); + feature.setProviderName(featureData.provider); + if (featureData.hasCustomHandler()) + feature.setInstallHandler(model.getFactory().createInstallHandler()); + + IFeatureInstallHandler handler = feature.getInstallHandler(); + if (handler != null) + handler.setLibrary(featureData.library); + if (createFeatureInfos) { + IFeatureInfo info = model.getFactory().createInfo(IFeature.INFO_COPYRIGHT); + feature.setFeatureInfo(info, IFeature.INFO_COPYRIGHT); + info.setURL("http://www.example.com/copyright"); //$NON-NLS-1$ + info.setDescription(PDEUIMessages.NewFeatureWizard_sampleCopyrightDesc); + + info = model.getFactory().createInfo(IFeature.INFO_LICENSE); + feature.setFeatureInfo(info, IFeature.INFO_LICENSE); + info.setURL("http://www.example.com/license"); //$NON-NLS-1$ + info.setDescription(PDEUIMessages.NewFeatureWizard_sampleLicenseDesc); + + info = model.getFactory().createInfo(IFeature.INFO_DESCRIPTION); + feature.setFeatureInfo(info, IFeature.INFO_DESCRIPTION); + info.setURL("http://www.example.com/description"); //$NON-NLS-1$ + info.setDescription(PDEUIMessages.NewFeatureWizard_sampleDescriptionDesc); + } + } + + @Override + protected void saveSettings(IDialogSettings settings) { + BundleProviderHistoryUtil.saveHistory(fFeatureProviderCombo, settings); + } +} 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 index 9142859ec8..51092d859c 100644 --- 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 @@ -18,20 +18,27 @@ import java.util.List; import java.util.Objects; +import org.apache.maven.artifact.Artifact; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.Platform; import org.eclipse.jface.preference.PreferenceDialog; 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.IWizardPage; import org.eclipse.jface.wizard.Wizard; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.m2e.pde.BNDInstructions; import org.eclipse.m2e.pde.MavenTargetLocation; import org.eclipse.m2e.pde.MavenTargetRepository; import org.eclipse.m2e.pde.MissingMetadataMode; +import org.eclipse.m2e.pde.TemplateFeatureModel; +import org.eclipse.m2e.pde.ui.Activator; import org.eclipse.pde.core.target.ITargetDefinition; import org.eclipse.pde.core.target.ITargetLocation; +import org.eclipse.pde.internal.core.ifeature.IFeature; import org.eclipse.pde.ui.target.ITargetLocationWizard; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CCombo; @@ -57,11 +64,16 @@ public class MavenTargetLocationWizard extends Wizard implements ITargetLocation private Button includeSource; private MavenTargetDependencyEditor dependencyEditor; private List repositoryList = new ArrayList<>(); + private Button createFeature; + private WizardPage page; + private FeatureSpecPage featureSpecPage; + private PluginListPage pluginListPage; public MavenTargetLocationWizard() { this(null); } + @SuppressWarnings("restriction") public MavenTargetLocationWizard(MavenTargetLocation targetLocation) { this.targetLocation = targetLocation; setWindowTitle(Messages.MavenTargetLocationWizard_0); @@ -70,7 +82,8 @@ public MavenTargetLocationWizard(MavenTargetLocation targetLocation) { repositoryList.add(mavenTargetRepository.copy()); } } - WizardPage page = new WizardPage( + + page = new WizardPage( targetLocation == null ? Messages.MavenTargetLocationWizard_1 : Messages.MavenTargetLocationWizard_2) { private Link editInstructionsButton; @@ -93,21 +106,43 @@ public void createControl(Composite parent) { createMetadataCombo(composite); new Label(composite, SWT.NONE).setText(Messages.MavenTargetLocationWizard_10); createScopeCombo(composite); - new Label(composite, SWT.NONE).setText(Messages.MavenTargetLocationWizard_8); - includeSource = new Button(composite, SWT.CHECK); + includeSource = createCheckBox(composite, Messages.MavenTargetLocationWizard_8); + createFeature = createCheckBox(composite, Messages.MavenTargetLocationWizard_13); + createFeature.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + updateUI(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + + } + }); if (targetLocation != null) { scope.setText(targetLocation.getDependencyScope()); metadata.setSelection(new StructuredSelection(targetLocation.getMetadataMode())); bndInstructions = targetLocation.getInstructions(null); includeSource.setSelection(targetLocation.isIncludeSource()); + IFeature template = targetLocation.getFeatureTemplate(); + createFeature.setSelection(template != null); } else { metadata.setSelection(new StructuredSelection(MavenTargetLocation.DEFAULT_METADATA_MODE)); bndInstructions = new BNDInstructions("", null); //$NON-NLS-1$ includeSource.setSelection(true); } + updateUI(); } + private Button createCheckBox(Composite composite, String text) { + new Label(composite, SWT.NONE); + Button box = new Button(composite, SWT.CHECK); + box.setText(text); + return box; + } + private void createScopeCombo(Composite parent) { Composite composite = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(2, false); @@ -119,9 +154,9 @@ private void createScopeCombo(Composite parent) { scopeLabel = new Label(composite, SWT.NONE); scopeLabel.setText(Messages.MavenTargetLocationWizard_15); scope.add(""); //$NON-NLS-1$ - scope.add("compile"); //$NON-NLS-1$ - scope.add("test"); //$NON-NLS-1$ - scope.add("provided"); //$NON-NLS-1$ + scope.add(Artifact.SCOPE_COMPILE); + scope.add(Artifact.SCOPE_PROVIDED); + scope.add(Artifact.SCOPE_TEST); scope.addModifyListener(new ModifyListener() { @Override @@ -179,21 +214,46 @@ private void updateUI() { editInstructionsButton.setVisible( metadata.getStructuredSelection().getFirstElement() == MissingMetadataMode.GENERATE); scopeLabel.setVisible(scope.getText().isBlank()); + getContainer().updateButtons(); } private CCombo combo(CCombo combo) { GridData data = new GridData(); - data.widthHint = 100; + data.widthHint = 120; combo.setLayoutData(data); return combo; } + }; page.setImageDescriptor(ImageDescriptor .createFromURL(MavenTargetLocationWizard.class.getResource("/icons/new_m2_project_wizard.gif"))); //$NON-NLS-1$ page.setTitle(page.getName()); page.setDescription(Messages.MavenTargetLocationWizard_23); addPage(page); + featureSpecPage = new FeatureSpecPage(targetLocation); + addPage(featureSpecPage); + pluginListPage = new PluginListPage(targetLocation); + addPage(pluginListPage); + } + + @Override + public boolean canFinish() { + if (isCreateFeature()) { + return page.isPageComplete(); + } + return super.canFinish(); + } + private boolean isCreateFeature() { + return createFeature == null || !createFeature.getSelection(); + } + + @Override + public IWizardPage getNextPage(IWizardPage page) { + if (isCreateFeature()) { + return null; + } + return super.getNextPage(page); } @Override @@ -210,7 +270,10 @@ public ITargetLocation[] getLocations() { public boolean performFinish() { List list; Collection excludes; - if (targetLocation == null) { + boolean iscreate = targetLocation == null; + boolean createFeature = this.createFeature.getSelection(); + TemplateFeatureModel featureModel = null; + if (iscreate) { excludes = Collections.emptyList(); if (bndInstructions == null) { list = Collections.emptyList(); @@ -229,17 +292,41 @@ public boolean performFinish() { if (bndInstructions != null) { list.add(bndInstructions); } + IFeature featureTemplate = targetLocation.getFeatureTemplate(); + if (featureTemplate != null) { + try { + featureModel = new TemplateFeatureModel(featureTemplate); + featureModel.load(); + } catch (CoreException e) { + Platform.getLog(MavenTargetLocationWizard.class).log(e.getStatus()); + } + } + } + if (createFeature) { + if (featureModel == null) { + featureModel = new TemplateFeatureModel(null); + } + try { + featureSpecPage.update(featureModel, iscreate); + pluginListPage.update(featureModel); + } catch (CoreException e) { + Platform.getLog(Activator.class).log(e.getStatus()); + e.printStackTrace(); + } + featureModel.makeReadOnly(); } + IFeature f = createFeature ? featureModel.getFeature() : null; MavenTargetLocation location = new MavenTargetLocation(dependencyEditor.getRoots(), repositoryList, (MissingMetadataMode) metadata.getStructuredSelection().getFirstElement(), scope.getText(), - includeSource.getSelection(), list, excludes); - if (targetLocation == null) { + includeSource.getSelection(), list, excludes, f); + if (iscreate) { targetLocation = location; } else { ITargetLocation[] locations = targetDefinition.getTargetLocations().clone(); for (int i = 0; i < locations.length; i++) { if (locations[i] == targetLocation) { locations[i] = location; + break; } } targetDefinition.setTargetLocations(locations); diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/Messages.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/Messages.java index 82837ad61b..c1787c2408 100644 --- a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/Messages.java +++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/Messages.java @@ -4,6 +4,8 @@ public class Messages extends NLS { private static final String BUNDLE_NAME = "org.eclipse.m2e.pde.ui.editor.messages"; //$NON-NLS-1$ + public static String NewFeatureWizard_PlugPage_desc; + public static String NewFeatureWizard_PlugPage_title; public static String MavenArtifactInstructionsWizard_0; public static String MavenArtifactInstructionsWizard_1; public static String MavenArtifactInstructionsWizard_2; @@ -20,6 +22,7 @@ public class Messages extends NLS { public static String MavenTargetDependencyEditor_5; public static String MavenTargetDependencyEditor_6; public static String MavenTargetLocationWizard_8; + public static String MavenTargetLocationWizard_13; public static String MavenTargetLocationWizard_9; public static String MavenTargetLocationWizard_10; public static String MavenTargetLocationWizard_11; diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/PluginListPage.java b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/PluginListPage.java new file mode 100644 index 0000000000..c291f6b9bb --- /dev/null +++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/PluginListPage.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * Copyright (c) 2000, 2021 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Lars Vogel - Bug 487943 + * Martin Karpisek - Bug 247265 + * Christoph Läubrich - adjust for m2e-pde usage + *******************************************************************************/ +package org.eclipse.m2e.pde.ui.editor; + +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.m2e.pde.MavenTargetLocation; +import org.eclipse.pde.core.plugin.IPluginBase; +import org.eclipse.pde.core.plugin.IPluginModelBase; +import org.eclipse.pde.core.plugin.PluginRegistry; +import org.eclipse.pde.internal.core.ICoreConstants; +import org.eclipse.pde.internal.core.PDECore; +import org.eclipse.pde.internal.core.feature.FeaturePlugin; +import org.eclipse.pde.internal.core.ifeature.IFeature; +import org.eclipse.pde.internal.core.ifeature.IFeatureModel; +import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin; +import org.eclipse.pde.internal.core.util.CoreUtility; +import org.eclipse.pde.internal.ui.IHelpContextIds; +import org.eclipse.pde.internal.ui.PDEPlugin; +import org.eclipse.pde.internal.ui.shared.CachedCheckboxTreeViewer; +import org.eclipse.pde.internal.ui.wizards.ListUtil; +import org.eclipse.pde.internal.ui.wizards.feature.BasePluginListPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.ui.PlatformUI; + +//derived from org.eclipse.pde.internal.ui.wizards.feature.PluginListPage +@SuppressWarnings("restriction") +public class PluginListPage extends BasePluginListPage { + + class PluginContentProvider implements ITreeContentProvider { + @Override + public Object[] getElements(Object parent) { + return getModels(); + } + + private IPluginModelBase[] getModels() { + return PluginRegistry.getActiveModels(); + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // If the PDE models are not initialized, initialize with option to cancel + if (newInput != null && !PDECore.getDefault().areModelsInitialized()) { + try { + getContainer().run(true, false, monitor -> { + // Target reloaded method clears existing models (which don't exist currently) + // and inits them with a progress monitor + PDECore.getDefault().getModelManager().targetReloaded(monitor); + if (monitor.isCanceled()) { + throw new InterruptedException(); + } + }); + } catch (InvocationTargetException | InterruptedException e) { + } + } + } + + @Override + public Object[] getChildren(Object parentElement) { + return new Object[0]; + } + + @Override + public Object getParent(Object element) { + return null; + } + + @Override + public boolean hasChildren(Object element) { + return false; + } + } + + private CheckboxTreeViewer pluginViewer; + private static final String S_INIT_LAUNCH = "initLaunch"; //$NON-NLS-1$ + private MavenTargetLocation targetLocation; + private Map id2version = new HashMap<>(); + + public PluginListPage(MavenTargetLocation targetLocation) { + super("pluginListPage"); //$NON-NLS-1$ + this.targetLocation = targetLocation; + setTitle(Messages.NewFeatureWizard_PlugPage_title); + setDescription(Messages.NewFeatureWizard_PlugPage_desc); + } + + @Override + public void createControl(Composite parent) { + Composite container = new Composite(parent, SWT.NULL); + GridLayout layout = new GridLayout(); + layout.numColumns = 3; + layout.verticalSpacing = 9; + container.setLayout(layout); + GridData gd; + + treePart.createControl(container, 4, true); + pluginViewer = treePart.getTreeViewer(); + PluginContentProvider provider = new PluginContentProvider(); + pluginViewer.setContentProvider(provider); + pluginViewer.setLabelProvider(PDEPlugin.getDefault().getLabelProvider()); + pluginViewer.setComparator(ListUtil.PLUGIN_COMPARATOR); + gd = (GridData) treePart.getControl().getLayoutData(); + gd.horizontalIndent = 0; + gd.heightHint = 250; + gd.widthHint = 300; + pluginViewer.setInput(PDECore.getDefault().getModelManager()); + treePart.setSelection(new Object[0]); + setControl(container); + Dialog.applyDialogFont(container); + PlatformUI.getWorkbench().getHelpSystem().setHelp(container, IHelpContextIds.NEW_FEATURE_REFERENCED_PLUGINS); + pluginViewer.addDoubleClickListener(event -> { + TreeItem firstTI = pluginViewer.getTree().getSelection()[0]; + treePart.getTreeViewer().setChecked(firstTI.getData(), !firstTI.getChecked()); + treePart.updateCounterLabel(); + }); + if (targetLocation != null) { + IFeature featureTemplate = targetLocation.getFeatureTemplate(); + if (featureTemplate != null) { + Map> map = Arrays.stream(featureTemplate.getPlugins()) + .collect(Collectors.groupingBy(IFeaturePlugin::getId)); + TreeItem[] items = pluginViewer.getTree().getItems(); + CachedCheckboxTreeViewer treeViewer = treePart.getTreeViewer(); + for (TreeItem item : items) { + IPluginModelBase model = (IPluginModelBase) item.getData(); + String id = model.getPluginBase().getId(); + List list = map.getOrDefault(id, Collections.emptyList()); + if (list.size() > 0) { + treeViewer.setChecked(model, true); + String definedVersions = list.stream().map(fp -> fp.getVersion()) + .filter(v -> !ICoreConstants.DEFAULT_VERSION.equals(v)).findAny().orElse(null); + if (definedVersions != null) { + id2version.put(id, definedVersions); + } + } + } + } + } + } + + // derived from + // org.eclipse.pde.internal.ui.wizards.feature.CreateFeatureProjectOperation.configureFeature(IFeature, + // WorkspaceFeatureModel) + public void update(IFeatureModel featureModel) throws CoreException { + Object[] selected = treePart.getTreeViewer().getCheckedLeafElements(); + IFeaturePlugin[] added = new IFeaturePlugin[selected.length]; + for (int i = 0; i < selected.length; i++) { + IPluginBase plugin = ((IPluginModelBase) selected[i]).getPluginBase(); + FeaturePlugin fplugin = (FeaturePlugin) featureModel.getFactory().createPlugin(); + fplugin.loadFrom(plugin); + fplugin.setVersion(id2version.getOrDefault(plugin.getId(), ICoreConstants.DEFAULT_VERSION)); + fplugin.setUnpack(CoreUtility.guessUnpack(plugin.getPluginModel().getBundleDescription())); + added[i] = fplugin; + } + IFeature feature = featureModel.getFeature(); + feature.removePlugins(feature.getPlugins()); + feature.addPlugins(added); + } + +} diff --git a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/messages.properties b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/messages.properties index ec96d3704f..5275831188 100644 --- a/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/messages.properties +++ b/org.eclipse.m2e.pde.ui/src/org/eclipse/m2e/pde/ui/editor/messages.properties @@ -25,3 +25,6 @@ MavenTargetLocationWizard_21=Edit the default instructions used in case of neces MavenTargetLocationWizard_23=Enter the desired maven artifact to add to the target platform MavenTargetLocationLabelProvider_1={0}:{1} ({2}) MavenTargetLocationLabelProvider_2={0} Maven Dependencies +MavenTargetLocationWizard_13=Generate Feature +NewFeatureWizard_PlugPage_title = Additionally referenced Plug-ins and Fragments +NewFeatureWizard_PlugPage_desc = Select the plug-ins and fragments from your workspace that should be additionally package into the generated feature. \ No newline at end of file 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 index d2137dd18e..bc8a6f655a 100644 --- 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Christoph Läubrich + * Copyright (c) 2018, 2021 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 @@ -12,23 +12,23 @@ *******************************************************************************/ package org.eclipse.m2e.pde.ui.provider; +import org.eclipse.jface.viewers.LabelProvider; 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 { +public class MavenTargetBundleLabelProvider extends LabelProvider { private Image image; - public MavenTargetBundleLabelProvider() { - super(true, false); + @Override + public String getText(Object element) { + return null; } @Override - public org.eclipse.swt.graphics.Image getImage(Object element) { + public Image getImage(Object element) { if (element instanceof MavenTargetBundle) { if (((MavenTargetBundle) element).isWrapped()) { Display current = Display.getCurrent(); @@ -39,7 +39,7 @@ public org.eclipse.swt.graphics.Image getImage(Object element) { return image; } } - return super.getImage(element); + return null; } @Override 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 index a7803f141b..1fab8342a8 100644 --- 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 @@ -40,7 +40,7 @@ public String getText(Object element) { return MessageFormat.format(Messages.MavenTargetLocationLabelProvider_2, roots.size()); } } - return String.valueOf(element); + return null; } @Override 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 index d218510e17..2579236710 100644 --- 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2020 Christoph Läubrich + * Copyright (c) 2018, 2021 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 @@ -37,8 +37,9 @@ public Object[] getChildren(Object parentElement) { Object[] array = roots.toArray(); return array; } else if (parentElement instanceof DependencyNode) { - DependencyNode[] dependencyNodes = ((DependencyNode) parentElement).getChildren() - .toArray(new DependencyNode[0]); + DependencyNode[] dependencyNodes = ((DependencyNode) parentElement).getChildren().stream() + .filter(d -> d.getArtifact().getFile() != null) + .toArray(DependencyNode[]::new); for (DependencyNode dependencyNode : dependencyNodes) { dependencyNode.setData(MavenTargetLocation.DEPENDENCYNODE_PARENT, parentElement); } diff --git a/org.eclipse.m2e.pde/META-INF/MANIFEST.MF b/org.eclipse.m2e.pde/META-INF/MANIFEST.MF index e6aecf74d9..90e23f54c1 100644 --- a/org.eclipse.m2e.pde/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.pde/META-INF/MANIFEST.MF @@ -10,10 +10,12 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.19.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.core, + org.eclipse.core.resources Export-Package: org.eclipse.m2e.pde Bundle-Activator: org.eclipse.m2e.pde.Activator Bundle-ActivationPolicy: lazy Import-Package: org.apache.commons.codec.digest;version="1.14.0", - org.apache.commons.io;version="2.6.0" + org.apache.commons.io;version="2.6.0", + org.apache.commons.io.output;version="2.8.0" Bundle-Vendor: Eclipse.org - m2e diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/DomXmlFeature.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/DomXmlFeature.java new file mode 100644 index 0000000000..4f4498b3c7 --- /dev/null +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/DomXmlFeature.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2021 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 org.eclipse.core.runtime.CoreException; +import org.eclipse.pde.internal.core.NLResourceHelper; +import org.eclipse.pde.internal.core.feature.AbstractFeatureModel; +import org.eclipse.pde.internal.core.feature.Feature; +import org.w3c.dom.Node; + +@SuppressWarnings("restriction") +public class DomXmlFeature extends Feature { + + public DomXmlFeature(Node node) { + DomXmlFeatureModel model = new DomXmlFeatureModel(); + setModel(model); + parse(node); + model.setEditable(false); + } + + @Override + public boolean isValid() { + return hasRequiredAttributes(); + } + + private static final class DomXmlFeatureModel extends AbstractFeatureModel { + + private boolean editable = true; + + @Override + public void load() throws CoreException { + + } + + public void setEditable(boolean editable) { + this.editable = editable; + } + + @Override + protected NLResourceHelper createNLResourceHelper() { + return null; + } + + @Override + public boolean isEditable() { + return editable; + } + + } + +} diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenFeaturePlugin.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenFeaturePlugin.java new file mode 100644 index 0000000000..de4f26e3a0 --- /dev/null +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenFeaturePlugin.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2021 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 org.eclipse.core.runtime.CoreException; +import org.eclipse.equinox.frameworkadmin.BundleInfo; +import org.eclipse.pde.core.target.TargetBundle; +import org.eclipse.pde.internal.core.feature.FeaturePlugin; +import org.eclipse.pde.internal.core.ifeature.IFeatureModel; + +@SuppressWarnings("restriction") +class MavenFeaturePlugin extends FeaturePlugin { + + private static final long serialVersionUID = -4864755319910409580L; + + MavenFeaturePlugin(TargetBundle child, IFeatureModel featureModel) throws CoreException { + setModel(featureModel); + BundleInfo bundleInfo = child.getBundleInfo(); + setId(bundleInfo.getSymbolicName()); + setVersion(bundleInfo.getVersion()); + setFragment(child.isFragment()); + setUnpack(false); + } + + @Override + public boolean exists() { + return true; + } +} diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenPomFeatureModel.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenPomFeatureModel.java new file mode 100644 index 0000000000..91c15d5972 --- /dev/null +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenPomFeatureModel.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2021 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.io.FileInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.maven.model.License; +import org.apache.maven.model.Model; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.m2e.core.MavenPlugin; +import org.eclipse.pde.internal.core.NLResourceHelper; +import org.eclipse.pde.internal.core.feature.AbstractFeatureModel; +import org.eclipse.pde.internal.core.feature.Feature; +import org.eclipse.pde.internal.core.feature.FeaturePlugin; +import org.eclipse.pde.internal.core.ifeature.IFeature; +import org.eclipse.pde.internal.core.ifeature.IFeatureInfo; +import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin; + +@SuppressWarnings("restriction") +class MavenPomFeatureModel extends AbstractFeatureModel { + + private static final boolean DEBUG_FEATURE_XML = false; + + private static final long serialVersionUID = 1L; + private boolean editable; + private Artifact artifact; + private TargetBundles targetBundles; + + MavenPomFeatureModel(Artifact artifact, TargetBundles bundles) { + this.artifact = artifact; + this.targetBundles = bundles; + } + + @Override + public IResource getUnderlyingResource() { + return null; + } + + @Override + public void load() throws CoreException { + editable = true; + try (FileInputStream stream = new FileInputStream(artifact.getFile())) { + Model model = MavenPlugin.getMaven().readModel(stream); + + IFeature f = getFeature(); + f.setId(model.getGroupId() + "." + model.getArtifactId() + "." + model.getPackaging()); + f.setVersion(TargetBundles.createOSGiVersion(model.getVersion()).toString()); + f.setLabel(model.getName()); + String description = model.getDescription(); + String url = model.getUrl(); + IFeatureInfo info = f.getModel().getFactory().createInfo(IFeature.INFO_DESCRIPTION); + info.setDescription(description); + info.setURL(url); + f.setFeatureInfo(info, info.getIndex()); + + List licenses = model.getLicenses(); + if (!licenses.isEmpty()) { + licenses.stream().map(license -> { + return Stream.builder().add(license.getName()).add(license.getUrl()) + .add(license.getComments()).build().filter(Objects::nonNull) + .filter(Predicate.not(String::isBlank)).collect(Collectors.joining("\r\n")); + }).collect(Collectors.joining("--------------------------------------------------\r\n")); + } + Optional dependencyNode = targetBundles.getDependencyNode(artifact); + MavenTargetBundle[] dependencies = dependencyNode.map(node -> { + PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); + node.accept(nlg); + return nlg; + }).map(nlg -> nlg.getArtifacts(true)).stream().flatMap(Collection::stream).filter(a -> a.getFile() != null) + .flatMap(a -> targetBundles.getTargetBundle(a).stream()).toArray(MavenTargetBundle[]::new); + IFeaturePlugin[] featurePlugins = new IFeaturePlugin[dependencies.length]; + for (int i = 0; i < featurePlugins.length; i++) { + FeaturePlugin plugin = new MavenFeaturePlugin(dependencies[i], this); + plugin.setParent(f); + featurePlugins[i] = plugin; + } + f.addPlugins(featurePlugins); + setEnabled(true); + updateTimeStampWith(System.currentTimeMillis()); + if (DEBUG_FEATURE_XML) { + File file = new File("/tmp/" + f.getId() + "/feature.xml"); + File installLocation = file.getParentFile(); + installLocation.mkdirs(); + try (PrintWriter writer = new PrintWriter(file, StandardCharsets.UTF_8)) { + f.write(" ", writer); + writer.flush(); + } + } + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, getClass(), "failed to load pom file")); + } finally { + editable = false; + + } + setLoaded(true); + } + + @Override + public String getInstallLocation() { + return null; + } + + @Override + public boolean isInSync() { + return true; + } + + @Override + public boolean isEditable() { + return editable; + } + + @Override + protected NLResourceHelper createNLResourceHelper() { + return null; + } + + @Override + public IFeature getFeature() { + if (feature == null) { + Feature f = new MavenPomFeature(this); + this.feature = f; + } + return feature; + } + + private static final class MavenPomFeature extends Feature { + + private static final long serialVersionUID = 1L; + + MavenPomFeature(MavenPomFeatureModel model) { + setModel(model); + } + + @Override + public boolean isValid() { + return hasRequiredAttributes(); + } + + } + +} 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 index 48c78ccc05..a2c67301c1 100644 --- a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetBundle.java +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetBundle.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2020 Christoph Läubrich + * Copyright (c) 2018, 2021 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 @@ -29,7 +29,6 @@ import aQute.bnd.osgi.Analyzer; import aQute.bnd.osgi.Jar; -import aQute.bnd.version.Version; public class MavenTargetBundle extends TargetBundle { @@ -132,7 +131,8 @@ public static TargetBundle getWrappedArtifact(Artifact artifact, Properties bndI analyzer.setProperty("mvnArtifactId", artifact.getArtifactId()); analyzer.setProperty("mvnVersion", artifact.getBaseVersion()); analyzer.setProperty("mvnClassifier", artifact.getClassifier()); - analyzer.setProperty("generatedOSGiVersion", createBundleVersion(artifact).toString()); + analyzer.setProperty("generatedOSGiVersion", + TargetBundles.createOSGiVersion(artifact).toString()); analyzer.setProperties(bndInstructions); jar.setManifest(analyzer.calcManifest()); jar.write(wrappedFile); @@ -169,24 +169,6 @@ private static boolean propertiesChanged(Properties properties, File file) { return true; } - public static Version createBundleVersion(Artifact artifact) { - String version = artifact.getVersion(); - if (version == null || version.isEmpty()) { - return new Version(0, 0, 1); - } - try { - int index = version.indexOf('-'); - if (index > -1) { - StringBuilder sb = new StringBuilder(version); - sb.setCharAt(index, '.'); - return Version.parseVersion(sb.toString()); - } - return Version.parseVersion(version); - } catch (IllegalArgumentException e) { - return new Version(0, 0, 1, version); - } - } - public boolean isWrapped() { return isWrapped; } diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetDependencyFilter.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetDependencyFilter.java new file mode 100644 index 0000000000..53ccd628da --- /dev/null +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetDependencyFilter.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2021 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 static org.apache.maven.artifact.Artifact.SCOPE_COMPILE; +import static org.apache.maven.artifact.Artifact.SCOPE_PROVIDED; +import static org.apache.maven.artifact.Artifact.SCOPE_RUNTIME; +import static org.apache.maven.artifact.Artifact.SCOPE_SYSTEM; +import static org.apache.maven.artifact.Artifact.SCOPE_TEST; + +import java.util.List; + +import org.eclipse.aether.graph.DependencyFilter; +import org.eclipse.aether.graph.DependencyNode; + +public class MavenTargetDependencyFilter implements DependencyFilter { + + private static final String[] VALID_EXTENSIONS = { "jar", "pom" }; + private boolean fetchTransitive; + private String locationScope; + + public MavenTargetDependencyFilter(boolean fetchTransitive, String scope) { + this.fetchTransitive = fetchTransitive; + this.locationScope = scope; + } + + @Override + public boolean accept(DependencyNode node, List parents) { + String extension = node.getArtifact().getExtension(); + for (String valid : VALID_EXTENSIONS) { + // only for a valid extension... + if (valid.equalsIgnoreCase(extension)) { + return (fetchTransitive || parents.size() <= 1) && isValidScope(node.getDependency().getScope()); + } + } + return false; + } + + private boolean isValidScope(String scope) { + if (locationScope == null || locationScope.isBlank() || scope == null || scope.isBlank()) { + return true; + } + if (SCOPE_COMPILE.equalsIgnoreCase(locationScope)) { + return SCOPE_COMPILE.equalsIgnoreCase(scope); + } + if (SCOPE_PROVIDED.equalsIgnoreCase(locationScope)) { + return SCOPE_PROVIDED.equalsIgnoreCase(scope) || SCOPE_COMPILE.equalsIgnoreCase(scope) + || SCOPE_SYSTEM.equalsIgnoreCase(scope) || SCOPE_RUNTIME.equalsIgnoreCase(scope); + } + if (SCOPE_TEST.equalsIgnoreCase(locationScope)) { + return SCOPE_TEST.equalsIgnoreCase(scope) || SCOPE_COMPILE.equalsIgnoreCase(scope) + || SCOPE_PROVIDED.equalsIgnoreCase(scope) || SCOPE_SYSTEM.equalsIgnoreCase(scope) + || SCOPE_RUNTIME.equalsIgnoreCase(scope); + } + return false; + } +} diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetFeature.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetFeature.java new file mode 100644 index 0000000000..dcf9b5a8dc --- /dev/null +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetFeature.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2021 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 org.eclipse.pde.core.target.TargetFeature; +import org.eclipse.pde.internal.core.ifeature.IFeatureModel; + +public class MavenTargetFeature extends TargetFeature { + + MavenTargetFeature(@SuppressWarnings("restriction") IFeatureModel model) { + super(model); + } + +} 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 index 91095a1e2e..f32d1f1a75 100644 --- a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocation.java +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocation.java @@ -12,20 +12,23 @@ *******************************************************************************/ package org.eclipse.m2e.pde; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; +import org.apache.commons.io.output.StringBuilderWriter; import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.repository.ArtifactRepository; import org.eclipse.aether.RepositoryException; @@ -49,19 +52,24 @@ import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.equinox.frameworkadmin.BundleInfo; 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.IModel; 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.ifeature.IFeature; +import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin; import org.eclipse.pde.internal.core.target.AbstractBundleContainer; @SuppressWarnings("restriction") public class MavenTargetLocation extends AbstractBundleContainer { + private static final String SOURCE_SUFFIX = ".source"; public static final String ELEMENT_CLASSIFIER = "classifier"; public static final String ELEMENT_TYPE = "type"; public static final String ELEMENT_VERSION = "version"; @@ -75,6 +83,7 @@ public class MavenTargetLocation extends AbstractBundleContainer { public static final String ELEMENT_REPOSITORY_ID = "id"; public static final String ELEMENT_REPOSITORY_URL = "url"; public static final String ELEMENT_REPOSITORIES = "repositories"; + public static final String ELEMENT_FEATURE = "feature"; public static final String ATTRIBUTE_INSTRUCTIONS_REFERENCE = "reference"; public static final String ATTRIBUTE_DEPENDENCY_SCOPE = "includeDependencyScope"; @@ -90,7 +99,6 @@ public class MavenTargetLocation extends AbstractBundleContainer { private final String dependencyScope; private final MissingMetadataMode metadataMode; private TargetBundles targetBundles; - private final Map> dependencyNodes = new ConcurrentHashMap<>(); private final Set excludedArtifacts = new HashSet<>(); private final Set failedArtifacts = new HashSet<>(); @@ -98,12 +106,14 @@ public class MavenTargetLocation extends AbstractBundleContainer { private final boolean includeSource; private final List roots; private final List extraRepositories; + private final IFeature featureTemplate; public MavenTargetLocation(Collection rootDependecies, Collection extraRepositories, MissingMetadataMode metadataMode, String dependencyScope, boolean includeSource, Collection instructions, - Collection excludes) { - this.roots = new ArrayList<>(rootDependecies); + Collection excludes, IFeature featureTemplate) { + this.featureTemplate = featureTemplate; + this.roots = new ArrayList(rootDependecies); this.extraRepositories = Collections.unmodifiableList(new ArrayList<>(extraRepositories)); this.metadataMode = metadataMode; this.dependencyScope = dependencyScope; @@ -120,7 +130,13 @@ public MavenTargetLocation(Collection rootDependecies, @Override protected TargetBundle[] resolveBundles(ITargetDefinition definition, IProgressMonitor monitor) throws CoreException { - if (targetBundles == null) { + return resolveArtifacts(definition, monitor).stream().flatMap(tb -> tb.bundles.entrySet().stream()) + .filter(e -> !isExcluded(e.getKey())).map(Entry::getValue).toArray(TargetBundle[]::new); + } + + private synchronized Optional resolveArtifacts(ITargetDefinition definition, + IProgressMonitor monitor) throws CoreException { + if (targetBundles == null && definition != null) { CacheManager cacheManager = CacheManager.forTargetHandle(definition.getHandle()); TargetBundles bundles = new TargetBundles(); IMaven maven = MavenPlugin.getMaven(); @@ -137,26 +153,71 @@ protected TargetBundle[] resolveBundles(ITargetDefinition definition, IProgressM } resolveDependency(root, maven, repositories, bundles, cacheManager, subMonitor.split(100)); } + if (featureTemplate != null) { + generateFeature(bundles, false); + if (includeSource) { + generateFeature(bundles, true); + } + } + Iterator iterator = bundles.features.stream().map(tf -> tf.getFeatureModel()).iterator(); + while (iterator.hasNext()) { + IModel model = iterator.next(); + model.load(); + } if (subMonitor.isCanceled()) { - return new TargetBundle[0]; + return Optional.empty(); } targetBundles = bundles; } - return targetBundles.bundles.entrySet().stream().filter(e -> !isExcluded(e.getKey())) - .map(Entry::getValue).toArray(TargetBundle[]::new); + return Optional.ofNullable(targetBundles); + } + + private void generateFeature(TargetBundles bundles, boolean source) throws CoreException { + Predicate bundleFilter = TargetBundle::isSourceBundle; + TemplateFeatureModel featureModel = new TemplateFeatureModel(featureTemplate); + featureModel.load(); + IFeature feature = featureModel.getFeature(); + if (source) { + feature.setId(feature.getId() + SOURCE_SUFFIX); + String label = feature.getLabel(); + if (label != null && !label.isBlank()) { + feature.setLabel(label + " (source)"); + } + for (IFeaturePlugin plugin : feature.getPlugins()) { + if (!plugin.getId().endsWith(SOURCE_SUFFIX)) { + feature.removePlugins(new IFeaturePlugin[] { plugin }); + } + } + } else { + bundleFilter = Predicate.not(bundleFilter); + } + Iterator featurePlugins = bundles.bundles.entrySet().stream() // + .filter(e -> !isExcluded(e.getKey()) && !isIgnored(e.getKey()))// + .map(Entry::getValue)// + .filter(bundleFilter)// + .sorted(Comparator.comparing(TargetBundle::getBundleInfo, + Comparator.comparing(BundleInfo::getSymbolicName))) + .iterator(); + while (featurePlugins.hasNext()) { + TargetBundle targetBundle = featurePlugins.next(); + feature.addPlugins(new IFeaturePlugin[] { new MavenFeaturePlugin(targetBundle, featureModel) }); + } + featureModel.makeReadOnly(); + bundles.features.add(new MavenTargetFeature(featureModel)); } public List getExtraRepositories() { return extraRepositories; } - private void resolveDependency(MavenTargetDependency root, IMaven maven, List repositories, + private Artifact resolveDependency(MavenTargetDependency root, IMaven maven, List repositories, TargetBundles targetBundles, CacheManager cacheManager, IProgressMonitor monitor) throws CoreException { Artifact artifact = RepositoryUtils.toArtifact(maven.resolve(root.getGroupId(), root.getArtifactId(), root.getVersion(), root.getType(), root.getClassifier(), repositories, monitor)); if (artifact != null) { - boolean isPomType = POM_PACKAGE_TYPE.equals(artifact.getExtension()); - if (isPomType || (dependencyScope != null && !dependencyScope.isBlank())) { + boolean isPomType = isPomType(artifact); + boolean fetchTransitive = dependencyScope != null && !dependencyScope.isBlank(); + if (isPomType || fetchTransitive) { IMavenExecutionContext context = maven.createExecutionContext(); PreorderNodeListGenerator dependecies = context.execute(new ICallable() { @@ -175,6 +236,8 @@ public PreorderNodeListGenerator call(IMavenExecutionContext context, IProgressM node.setData(DEPENDENCYNODE_ROOT, root); DependencyRequest dependencyRequest = new DependencyRequest(); dependencyRequest.setRoot(node); + dependencyRequest + .setFilter(new MavenTargetDependencyFilter(fetchTransitive, dependencyScope)); repoSystem.resolveDependencies(context.getRepositorySession(), dependencyRequest); PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); node.accept(nlg); @@ -189,22 +252,30 @@ public PreorderNodeListGenerator call(IMavenExecutionContext context, IProgressM } } }, monitor); - for (Artifact a : dependecies.getArtifacts(true)) { + if (a.getFile() == null) { + // this is a filtered dependency + continue; + } addBundleForArtifact(a, cacheManager, maven, targetBundles); } - dependencyNodes.put(root, dependecies.getNodes()); + targetBundles.dependencyNodes.put(root, dependecies.getNodes()); } else { addBundleForArtifact(artifact, cacheManager, maven, targetBundles); } } + return artifact; + } + + private boolean isPomType(Artifact artifact) { + return POM_PACKAGE_TYPE.equals(artifact.getExtension()); } private void addBundleForArtifact(Artifact artifact, CacheManager cacheManager, IMaven maven, TargetBundles targetBundles) { - if (POM_PACKAGE_TYPE.equals(artifact.getExtension())) { - // pom typed artifacts are not for bundeling --> TODO we should generate a - // feature from them! + if (isPomType(artifact)) { + MavenTargetFeature feature = new MavenTargetFeature(new MavenPomFeatureModel(artifact, targetBundles)); + targetBundles.features.add(feature); return; } BNDInstructions bndInstructions = instructionsMap.get(getKey(artifact)); @@ -239,7 +310,7 @@ private void addBundleForArtifact(Artifact artifact, CacheManager cacheManager, public MavenTargetLocation update(IProgressMonitor monitor) throws CoreException { - List latest = new ArrayList<>(); + List latest = new ArrayList(); int updated = 0; for (MavenTargetDependency dependency : roots) { Artifact artifact = new DefaultArtifact( @@ -284,7 +355,7 @@ public VersionRangeResult call(IMavenExecutionContext context, IProgressMonitor } return new MavenTargetLocation(latest, extraRepositories, metadataMode, dependencyScope, includeSource, - instructionsMap.values(), excludedArtifacts); + instructionsMap.values(), excludedArtifacts, featureTemplate); } @@ -294,7 +365,11 @@ public List getRoots() { public MavenTargetLocation withInstructions(Collection instructions) { return new MavenTargetLocation(roots, extraRepositories, metadataMode, dependencyScope, includeSource, - instructions, excludedArtifacts); + instructions, excludedArtifacts, featureTemplate); + } + + public IFeature getFeatureTemplate() { + return featureTemplate; } public BNDInstructions getInstructions(Artifact artifact) { @@ -327,15 +402,18 @@ public int getDependencyCount() { } List getDependencyNodes(MavenTargetDependency dependency) { - return dependencyNodes.get(dependency); + TargetBundles bundles = targetBundles; + if (bundles == null) { + return Collections.emptyList(); + } + return bundles.dependencyNodes.get(dependency); } @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[] {}; + return resolveArtifacts(definition, monitor).stream().flatMap(tb -> tb.features.stream()) + .toArray(TargetFeature[]::new); } @Override @@ -350,7 +428,7 @@ public String getLocation(boolean resolve) throws CoreException { @Override public int hashCode() { - return Objects.hash(roots, dependencyNodes, dependencyScope, failedArtifacts, metadataMode); + return Objects.hash(roots, dependencyScope, failedArtifacts, metadataMode); } @Override @@ -365,8 +443,7 @@ public boolean equals(Object obj) { return false; } MavenTargetLocation other = (MavenTargetLocation) obj; - return Objects.equals(roots, other.roots) && Objects.equals(dependencyNodes, other.dependencyNodes) - && Objects.equals(dependencyScope, other.dependencyScope) + return Objects.equals(roots, other.roots) && Objects.equals(dependencyScope, other.dependencyScope) && Objects.equals(failedArtifacts, other.failedArtifacts); } @@ -383,6 +460,12 @@ public String serialize() { attribute(xml, ATTRIBUTE_DEPENDENCY_SCOPE, dependencyScope); attribute(xml, ATTRIBUTE_INCLUDE_SOURCE, includeSource ? "true" : ""); xml.append(">"); + if (featureTemplate != null) { + try (PrintWriter writer = new PrintWriter(new StringBuilderWriter(xml))) { + featureTemplate.write("", writer); + writer.flush(); + } + } if (!roots.isEmpty()) { xml.append("<" + ELEMENT_DEPENDENCIES + ">"); roots.stream().sorted(Comparator.comparing(MavenTargetDependency::getKey)).forEach(dependency -> { @@ -416,9 +499,12 @@ public String serialize() { xml.append(instructions); xml.append("\r\n]]>"); }); - excludedArtifacts.stream().sorted().forEach(ignored -> element(xml, ELEMENT_EXCLUDED, ignored)); + excludedArtifacts.stream().sorted().forEach(ignored -> { + element(xml, ELEMENT_EXCLUDED, ignored); + }); xml.append(""); - return xml.toString(); + String string = xml.toString(); + return string; } private static void element(StringBuilder xml, String name, String value) { @@ -452,7 +538,6 @@ public MissingMetadataMode getMetadataMode() { } public void refresh() { - dependencyNodes.clear(); targetBundles = null; clearResolutionStatus(); } @@ -491,22 +576,15 @@ public void setExcluded(Artifact artifact, boolean disabled) { public MavenTargetBundle getMavenTargetBundle(Artifact artifact) { TargetBundles bundles = targetBundles; if (bundles != null) { - TargetBundle targetBundle = bundles.bundles.get(artifact); - if (targetBundle instanceof MavenTargetBundle) { - return (MavenTargetBundle) targetBundle; - } + return bundles.getTargetBundle(artifact).orElse(null); } return null; } public MavenTargetBundle getMavenTargetBundle(MavenTargetDependency dependency) { - List list = dependencyNodes.get(dependency); - if (list != null) { - for (DependencyNode node : list) { - if (node.getData().get(DEPENDENCYNODE_ROOT) == dependency) { - return getMavenTargetBundle(node.getArtifact()); - } - } + TargetBundles bundles = targetBundles; + if (bundles != null) { + return bundles.getTargetBundle(dependency).orElse(null); } return null; } 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 index 2000215abf..f5d0f3c69f 100644 --- a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocationFactory.java +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/MavenTargetLocationFactory.java @@ -16,6 +16,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.stream.IntStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -25,6 +26,7 @@ import org.eclipse.core.runtime.Status; import org.eclipse.pde.core.target.ITargetLocation; import org.eclipse.pde.core.target.ITargetLocationFactory; +import org.eclipse.pde.internal.core.ifeature.IFeature; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -97,9 +99,13 @@ public ITargetLocation getTargetLocation(String type, String serializedXML) thro excludes.add(((Element) item).getTextContent()); } } + NodeList featuresNodeList = location.getElementsByTagName(MavenTargetLocation.ELEMENT_FEATURE); + IFeature templateFeature = IntStream.range(0, featuresNodeList.getLength()) + .mapToObj(index -> featuresNodeList.item(index)).map(DomXmlFeature::new).findFirst().orElse(null); + return new MavenTargetLocation(dependencies, repositories, mode, dependencyScope, Boolean.parseBoolean(location.getAttribute(MavenTargetLocation.ATTRIBUTE_INCLUDE_SOURCE)), - instructions, excludes); + instructions, excludes, templateFeature); } catch (Exception e) { throw new CoreException(new Status(IStatus.ERROR, MavenTargetLocationFactory.class.getPackage().getName(), e.getMessage(), e)); diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/TargetBundles.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/TargetBundles.java index cb80eb1cb8..f1aa0664e5 100644 --- a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/TargetBundles.java +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/TargetBundles.java @@ -12,13 +12,21 @@ *******************************************************************************/ package org.eclipse.m2e.pde; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import org.apache.maven.model.Model; import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.graph.DependencyNode; import org.eclipse.pde.core.target.TargetBundle; +import org.eclipse.pde.core.target.TargetFeature; + +import aQute.bnd.version.Version; /** * represents a resolved set of {@link Artifact} -> {@link TargetBundle} @@ -26,4 +34,56 @@ class TargetBundles { final Map bundles = new HashMap<>(); final Set ignoredArtifacts = new HashSet<>(); + final List features = new ArrayList<>(); + final Map> dependencyNodes = new HashMap<>(); + + Optional getDependencyNode(Artifact artifact) { + return dependencyNodes.values().stream().flatMap(l -> l.stream()) + .filter(node -> artifact.equals(node.getArtifact())).findAny(); + } + + Optional getTargetBundle(Artifact artifact) { + TargetBundle targetBundle = bundles.get(artifact); + if (targetBundle instanceof MavenTargetBundle) { + return Optional.of((MavenTargetBundle) targetBundle); + } + return Optional.empty(); + } + + Optional getTargetBundle(MavenTargetDependency dependency) { + List list = dependencyNodes.get(dependency); + if (list != null) { + Optional artifact = list.stream() + .filter(node -> node.getData().get(MavenTargetLocation.DEPENDENCYNODE_ROOT) == dependency) + .findFirst().map(DependencyNode::getArtifact); + return artifact.flatMap(this::getTargetBundle); + } + return Optional.empty(); + } + + public static Version createOSGiVersion(Artifact artifact) { + String version = artifact.getVersion(); + return createOSGiVersion(version); + } + + public static Version createOSGiVersion(Model model) { + return createOSGiVersion(model.getVersion()); + } + + public static Version createOSGiVersion(String version) { + if (version == null || version.isEmpty()) { + return new Version(0, 0, 1); + } + try { + int index = version.indexOf('-'); + if (index > -1) { + StringBuilder sb = new StringBuilder(version); + sb.setCharAt(index, '.'); + return Version.parseVersion(sb.toString()); + } + return Version.parseVersion(version); + } catch (IllegalArgumentException e) { + return new Version(0, 0, 1, version); + } + } } diff --git a/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/TemplateFeatureModel.java b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/TemplateFeatureModel.java new file mode 100644 index 0000000000..e43f345a0b --- /dev/null +++ b/org.eclipse.m2e.pde/src/org/eclipse/m2e/pde/TemplateFeatureModel.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2021 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.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.pde.internal.core.NLResourceHelper; +import org.eclipse.pde.internal.core.feature.AbstractFeatureModel; +import org.eclipse.pde.internal.core.feature.Feature; +import org.eclipse.pde.internal.core.ifeature.IFeature; + +/** + * creates a new model by copy the a given {@link IFeature} as its template + */ +@SuppressWarnings("restriction") +public final class TemplateFeatureModel extends AbstractFeatureModel { + + private static final long serialVersionUID = 1L; + private String xml; + private boolean editable = true; + + public TemplateFeatureModel(IFeature template) { + if (template != null) { + StringWriter stringWriter = new StringWriter(); + try (PrintWriter writer = new PrintWriter(stringWriter)) { + template.write("", writer); + writer.flush(); + } + this.xml = stringWriter.toString(); + } + } + + @Override + public synchronized void load() throws CoreException { + if (xml != null && isEditable()) { + load(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)), false); + updateTimeStampWith(System.currentTimeMillis()); + setLoaded(true); + xml = null; + } + } + + @Override + protected NLResourceHelper createNLResourceHelper() { + return null; + } + + @Override + public boolean isEditable() { + return editable; + } + + public void makeReadOnly() { + this.editable = false; + } + + @Override + public IFeature getFeature() { + if (feature == null) { + feature = new TemplateFeature(this); + } + return feature; + } + + private static final class TemplateFeature extends Feature { + + public TemplateFeature(TemplateFeatureModel templateFeatureModel) { + setModel(templateFeatureModel); + } + + @Override + public boolean isValid() { + return hasRequiredAttributes(); + } + } +} diff --git a/target-platform/target-platform.target b/target-platform/target-platform.target index e27bb104d6..bb1324c728 100644 --- a/target-platform/target-platform.target +++ b/target-platform/target-platform.target @@ -4,7 +4,7 @@ - +