diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/ManifestUtils.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/ManifestUtils.java index f2b7db192e..a1990dd793 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/ManifestUtils.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/ManifestUtils.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2018 IBM Corporation and others. + * Copyright (c) 2006, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -10,9 +10,13 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Hannes Wellmann - Add utility method to derive the BREE of a resource from its EE requirements *******************************************************************************/ package org.eclipse.pde.internal.core.util; +import static org.osgi.framework.namespace.ExecutionEnvironmentNamespace.CAPABILITY_VERSION_ATTRIBUTE; +import static org.osgi.framework.namespace.ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -24,7 +28,10 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Consumer; import java.util.jar.JarFile; +import java.util.regex.Pattern; +import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -32,24 +39,34 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.launching.environments.IExecutionEnvironmentsManager; import org.eclipse.osgi.util.ManifestElement; import org.eclipse.osgi.util.NLS; import org.eclipse.pde.core.build.IBuild; import org.eclipse.pde.core.build.IBuildEntry; import org.eclipse.pde.internal.core.ICoreConstants; import org.eclipse.pde.internal.core.PDECore; +import org.eclipse.pde.internal.core.TargetPlatformHelper; import org.eclipse.pde.internal.core.TargetWeaver; import org.eclipse.pde.internal.core.build.WorkspaceBuildModel; import org.eclipse.pde.internal.core.ibundle.IManifestHeader; import org.eclipse.pde.internal.core.project.PDEProject; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.namespace.ExecutionEnvironmentNamespace; +import org.osgi.resource.Namespace; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; public class ManifestUtils { @@ -77,7 +94,7 @@ public class ManifestUtils { *

* If this method is being called from a dev mode workspace, the returned map * should be passed to {@link TargetWeaver#weaveManifest(Map, File)} so that the - * bundle classpath can be corrected. + * bundle classpath can be corrected. *

*

* This method is called by @@ -285,4 +302,95 @@ private static String splitOnComma(String value) { sb.append(values[values.length - 1]); return sb.toString(); } + + /** + * Returns the list all execution-environments required by the given OSGi + * resource. Only registered EEs are considered. + * + * @param resource + * the osgi resource + * @return a list containing the id's of all required EEs + * @see ExecutionEnvironmentNamespace#EXECUTION_ENVIRONMENT_NAMESPACE + * @see IExecutionEnvironmentsManager#getExecutionEnvironments() + */ + public static Stream getRequiredExecutionEnvironments(Resource resource) { + List requirements = resource.getRequirements(EXECUTION_ENVIRONMENT_NAMESPACE); + return requirements.stream() + .map(requirement -> requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE)) + .mapMulti(ManifestUtils::parseRequiredEEsFromFilter); + } + + // provide fast-path for simple filters like: (&(osgi.ee=JavaSE)(version=17) + private static final Map SIMPLE_EE_FILTERS = new HashMap<>(); + private static final Map> AVAILABLE_EE_ATTRIBUTES = new HashMap<>(); + + static { + // Manually add ancient EE id that are difficult to parse + AVAILABLE_EE_ATTRIBUTES.put("CDC-1.0/Foundation-1.0", //$NON-NLS-1$ + Map.of(EXECUTION_ENVIRONMENT_NAMESPACE, "CDC/Foundation", CAPABILITY_VERSION_ATTRIBUTE, "1.0")); //$NON-NLS-1$ //$NON-NLS-2$ + AVAILABLE_EE_ATTRIBUTES.put("CDC-1.1/Foundation-1.1", //$NON-NLS-1$ + Map.of(EXECUTION_ENVIRONMENT_NAMESPACE, "CDC/Foundation", CAPABILITY_VERSION_ATTRIBUTE, "1.1")); //$NON-NLS-1$ //$NON-NLS-2$ + + for (String eeId : TargetPlatformHelper.getKnownExecutionEnvironments()) { + String eeName; + String eeVersion; + // Extract the osgi.ee name and version attribute from the EE id + Map predefinedAttributes = AVAILABLE_EE_ATTRIBUTES.get(eeId); + if (predefinedAttributes == null) { + if (eeId.indexOf('/') >= eeId.indexOf('-')) { + ILog.get().error("Cannot reliably parse filter attributes from BREE with id: " + eeId); //$NON-NLS-1$ + continue; + } + int versionSeparator = eeId.lastIndexOf('-'); + if (versionSeparator < 0) { + throw new IllegalArgumentException("Missing version-separator in EE Id"); //$NON-NLS-1$ + } + eeName = eeId.substring(0, versionSeparator); + // OSGi spec chapter 3.4.1 Bundle-RequiredExecutionEnvironment + if ("J2SE".equals(eeName)) { //$NON-NLS-1$ + eeName = "JavaSE"; //$NON-NLS-1$ + } + eeVersion = eeId.substring(versionSeparator + 1); + + AVAILABLE_EE_ATTRIBUTES.put(eeId, + Map.of(EXECUTION_ENVIRONMENT_NAMESPACE, eeName, CAPABILITY_VERSION_ATTRIBUTE, eeVersion)); + } else { + eeName = predefinedAttributes.get(EXECUTION_ENVIRONMENT_NAMESPACE); + eeVersion = predefinedAttributes.get(CAPABILITY_VERSION_ATTRIBUTE); + } + String eeNamespace = EXECUTION_ENVIRONMENT_NAMESPACE; + String versionAttribute = CAPABILITY_VERSION_ATTRIBUTE; + String filter1 = "(&(" + eeNamespace + "=" + eeName + ")(" + versionAttribute + "=" + eeVersion + "))"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + String filter2 = "(&(" + versionAttribute + "=" + eeVersion + ")(" + eeNamespace + "=" + eeName + "))"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + SIMPLE_EE_FILTERS.put(filter1, eeId); // add both variants + SIMPLE_EE_FILTERS.put(filter2, eeId); + } + } + + private static final Pattern WHITESPACE = Pattern.compile("\\s*"); //$NON-NLS-1$ + + public static void parseRequiredEEsFromFilter(String eeFilter, Consumer collector) { + if (eeFilter == null) { + return; + } + if (eeFilter.chars().anyMatch(Character::isWhitespace)) { + eeFilter = WHITESPACE.matcher(eeFilter).replaceAll(""); //$NON-NLS-1$ + } + String ee = SIMPLE_EE_FILTERS.get(eeFilter); + if (ee != null) { + collector.accept(ee); + } else { + try { // complex filter. Collect all matching EEs + Filter filter = FrameworkUtil.createFilter(eeFilter); + AVAILABLE_EE_ATTRIBUTES.forEach((eeId, eeAttributes) -> { + if (filter.matches(eeAttributes)) { + collector.accept(eeId); + } + }); + } catch (InvalidSyntaxException e) { // should not happen + throw new IllegalArgumentException("Invalid execution environment filter", e); //$NON-NLS-1$ + } + } + } + } diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/model/bundle/ExecutionEnvironmentTestCase.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/model/bundle/ExecutionEnvironmentTestCase.java index c5339cb56e..fdb1b684ef 100644 --- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/model/bundle/ExecutionEnvironmentTestCase.java +++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/model/bundle/ExecutionEnvironmentTestCase.java @@ -16,10 +16,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.eclipse.pde.internal.core.ibundle.IManifestHeader; import org.eclipse.pde.internal.core.text.bundle.RequiredExecutionEnvironmentHeader; +import org.eclipse.pde.internal.core.util.ManifestUtils; import org.eclipse.text.edits.TextEdit; import org.junit.Test; import org.osgi.framework.Constants; @@ -218,6 +221,33 @@ public void testPreserveSpacing() throws Exception { assertEquals(expected.toString(), fDocument.get(pos, length)); } + @Test + public void testManifestUtils_parseRequiredEEsFromFilter() throws Exception { + { + Set ees = new HashSet<>(); + ManifestUtils.parseRequiredEEsFromFilter("(&(osgi.ee=JavaSE)(version=1.8))", ees::add); + assertEquals(Set.of("JavaSE-1.8"), ees); + } + { + Set ees = new HashSet<>(); + ManifestUtils.parseRequiredEEsFromFilter("(& ( version=17 ) ( osgi.ee=JavaSE ) )", ees::add); + assertEquals(Set.of("JavaSE-17"), ees); + } + { + Set ees = new HashSet<>(); + ManifestUtils.parseRequiredEEsFromFilter("(&(osgi.ee=JavaSE)(version>=1.6)(version<=1.8))", ees::add); + assertEquals(Set.of("JavaSE-1.6", "JavaSE-1.7", "JavaSE-1.8"), ees); + } + { + // Equivalent to + // Bundle-RequiredExecutionEnvironment: JavaSE-17, JavaSE-21 + Set ees = new HashSet<>(); + ManifestUtils.parseRequiredEEsFromFilter( + "(| (&(version=17)(osgi.ee=JavaSE)) (&(osgi.ee=JavaSE)(version=21)) )", ees::add); + assertEquals(Set.of("JavaSE-17", "JavaSE-21"), ees); + } + } + private RequiredExecutionEnvironmentHeader getRequiredExecutionEnvironmentHeader() { return (RequiredExecutionEnvironmentHeader) fModel.getBundle() .getManifestHeader(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);