From 1b1b92a32bef5fbb53fd71048ce2be8e579c03fb Mon Sep 17 00:00:00 2001 From: Hannes Wellmann Date: Wed, 14 Feb 2024 00:14:28 +0100 Subject: [PATCH] Add utility to derive the BREE of a resource from its EE requirements In general the strategy is to consider all osgi.ee requirements of a resource and parse their filter. For the case of a simple filter like '(&(osgi.ee=JavaSE)(version=17)', probably the majority of cases, a fast-path is provided where the filter is just looked up in a map. Otherwise each registered execution-environment is matched against the filter and the list of matching EEs is returned. The new method is intended to support the migration from the Equinox resolver to the OSGi Resources/Wiring API. --- .../pde/internal/core/util/ManifestUtils.java | 112 +++++++++++++++++- .../bundle/ExecutionEnvironmentTestCase.java | 30 +++++ 2 files changed, 140 insertions(+), 2 deletions(-) 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);