Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utility to derive the BREE of a resource from its EE requirements #1325

Merged
merged 1 commit into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -24,32 +28,45 @@
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;

import org.eclipse.core.resources.IFile;
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 {

Expand Down Expand Up @@ -77,7 +94,7 @@ public class ManifestUtils {
* <p>
* 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.
* </p>
* <p>
* This method is called by
Expand Down Expand Up @@ -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<String> getRequiredExecutionEnvironments(Resource resource) {
List<Requirement> 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<String, String> SIMPLE_EE_FILTERS = new HashMap<>();
private static final Map<String, Map<String, String>> 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<String, String> 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<String> 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$
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -218,6 +221,33 @@ public void testPreserveSpacing() throws Exception {
assertEquals(expected.toString(), fDocument.get(pos, length));
}

@Test
public void testManifestUtils_parseRequiredEEsFromFilter() throws Exception {
{
Set<String> ees = new HashSet<>();
ManifestUtils.parseRequiredEEsFromFilter("(&(osgi.ee=JavaSE)(version=1.8))", ees::add);
assertEquals(Set.of("JavaSE-1.8"), ees);
}
{
Set<String> ees = new HashSet<>();
ManifestUtils.parseRequiredEEsFromFilter("(& ( version=17 ) ( osgi.ee=JavaSE ) )", ees::add);
assertEquals(Set.of("JavaSE-17"), ees);
}
{
Set<String> 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<String> 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);
Expand Down
Loading