All Downloads are FREE. Search and download functionalities are using the official Maven repository.

aQute.bnd.osgi.resource.ResourceUtils Maven / Gradle / Ivy

The newest version!
package aQute.bnd.osgi.resource;

import static aQute.bnd.exceptions.FunctionWithException.asFunction;
import static aQute.lib.comparators.Comparators.compare;
import static aQute.lib.comparators.Comparators.compareMapsByKeys;
import static aQute.lib.comparators.Comparators.comparePresent;
import static aQute.lib.comparators.Comparators.isFinal;
import static java.lang.invoke.MethodHandles.publicLookup;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.namespace.AbstractWiringNamespace;
import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.namespace.NativeNamespace;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.namespace.contract.ContractNamespace;
import org.osgi.namespace.extender.ExtenderNamespace;
import org.osgi.namespace.implementation.ImplementationNamespace;
import org.osgi.namespace.service.ServiceNamespace;
import org.osgi.resource.Capability;
import org.osgi.resource.Namespace;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.service.repository.ContentNamespace;
import org.osgi.service.repository.Repository;

import aQute.bnd.build.model.clauses.VersionedClause;
import aQute.bnd.header.Attrs;
import aQute.bnd.memoize.Memoize;
import aQute.bnd.osgi.BundleId;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Macro;
import aQute.bnd.osgi.Processor;
import aQute.bnd.osgi.resource.FilterParser.Expression;
import aQute.bnd.osgi.resource.FilterParser.PackageExpression;
import aQute.bnd.service.library.LibraryNamespace;
import aQute.bnd.stream.MapStream;
import aQute.bnd.unmodifiable.Lists;
import aQute.bnd.unmodifiable.Maps;
import aQute.bnd.unmodifiable.Sets;
import aQute.bnd.version.Version;
import aQute.lib.comparators.Comparators;
import aQute.lib.converter.Converter;
import aQute.lib.strings.Strings;

public abstract class ResourceUtils {

	public static final Comparator			REQUIREMENT_COMPARATOR		= ResourceUtils::compareTo;
	public static final Comparator	IDENTITY_VERSION_COMPARATOR	=								//
		(o1, o2) -> {

			int n = comparePresent(o1, o2);
			if (isFinal(n))
				return n;

			String v1 = getIdentityVersion(o1);
			String v2 = getIdentityVersion(o2);
			return n = compare(v1, v2);
		};

	private static final Comparator	RESOURCE_COMPARATOR			= ResourceUtils::compareTo;
	public static final Resource						DUMMY_RESOURCE				= new ResourceBuilder().build();
	public static final String							WORKSPACE_NAMESPACE			= "bnd.workspace.project";

	private static final Converter						cnv							= new Converter()
		.hook(Version.class, (dest, o) -> toVersion(o));


	public interface IdentityCapability extends Capability {
		public enum Type {
			bundle(IdentityNamespace.TYPE_BUNDLE),
			fragment(IdentityNamespace.TYPE_FRAGMENT),
			unknown(IdentityNamespace.TYPE_UNKNOWN);

			private String s;

			Type(String s) {
				this.s = s;
			}

			@Override
			public String toString() {
				return s;
			}

		}

		String osgi_identity();

		boolean singleton();

		Version version();

		Type type();

		URI uri();

		String copyright();

		String description(String string);

		String documentation();

		String license();
	}

	public interface ContentCapability extends Capability {
		String osgi_content();

		URI url();

		long size();

		String mime();
	}

	public interface BundleCap extends Capability {
		String osgi_wiring_bundle();

		boolean singleton();

		Version bundle_version();
	}

	public static Stream capabilityStream(Resource resource, String namespace) {
		return resource.getCapabilities(namespace)
			.stream();
	}

	public static  Stream capabilityStream(Resource resource, String namespace,
		Class type) {
		return capabilityStream(resource, namespace).map(c -> as(c, type));
	}

	public static ContentCapability getContentCapability(Resource resource) {
		return capabilityStream(resource, ContentNamespace.CONTENT_NAMESPACE, ContentCapability.class).findFirst()
			.orElse(null);
	}

	public static Optional getURI(Resource resource) {
		return capabilityStream(resource, ContentNamespace.CONTENT_NAMESPACE, ContentCapability.class).findFirst()
			.map(ContentCapability::url);
	}

	public static List getContentCapabilities(Resource resource) {
		return capabilityStream(resource, ContentNamespace.CONTENT_NAMESPACE, ContentCapability.class)
			.collect(toCapabilities());
	}

	public static IdentityCapability getIdentityCapability(Resource resource) {
		return capabilityStream(resource, IdentityNamespace.IDENTITY_NAMESPACE, IdentityCapability.class).findFirst()
			.orElse(null);
	}

	public static BundleId getBundleId(Resource resource) {
		BundleCap b = getBundleCapability(resource);
		if (b != null && b.osgi_wiring_bundle() != null)
			return new BundleId(b.osgi_wiring_bundle(), b.bundle_version());

		//
		// might not be a bundle. Since Maven is our primary repo,
		// we can try to find the common ways the bsn is simulated.
		// in maven repos.
		//

		IdentityCapability identity = ResourceUtils.getIdentityCapability(resource);
		if (identity != null) {
			String bsn = identity.osgi_identity();
			Version version = identity.version();
			if (version == null)
				version = Version.LOWEST;

			if (bsn != null) {
				return new BundleId(bsn, version.toString());
			}
		}

		List capabilities = resource.getCapabilities("bnd.info");
		if (capabilities.isEmpty())
			return null;

		Capability cap = capabilities.get(0);
		String bsn = (String) cap.getAttributes()
			.get("name");
		Version version = getVersion(cap);
		if (version == null)
			version = Version.LOWEST;
		if (bsn == null)
			return null;

		return new BundleId(bsn, version);
	}

	public static String getIdentityVersion(Resource resource) {
		return capabilityStream(resource, IdentityNamespace.IDENTITY_NAMESPACE, IdentityCapability.class).findFirst()
			.map(c -> c.getAttributes()
				.get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE))
			.map(Object::toString)
			.orElse(null);
	}

	public static BundleCap getBundleCapability(Resource resource) {
		return capabilityStream(resource, BundleNamespace.BUNDLE_NAMESPACE, BundleCap.class).findFirst()
			.orElse(null);
	}

	public static Version toVersion(Object v) {
		if (v instanceof Version version)
			return version;

		if (v instanceof org.osgi.framework.Version o) {
			String q = o.getQualifier();
			return q.isEmpty() ? new Version(o.getMajor(), o.getMinor(), o.getMicro())
				: new Version(o.getMajor(), o.getMinor(), o.getMicro(), q);
		}

		if ((v instanceof String string) && Version.isVersion(string)) {
			return Version.valueOf(string);
		}

		return null;
	}

	public static Version getVersion(Capability cap) {
		String attr = getVersionAttributeForNamespace(cap.getNamespace());
		if (attr == null)
			return null;
		Object v = cap.getAttributes()
			.get(attr);
		return toVersion(v);
	}

	public static URI getURI(Capability contentCapability) {
		Object uriObj = contentCapability.getAttributes()
			.get(ContentNamespace.CAPABILITY_URL_ATTRIBUTE);
		if (uriObj == null)
			return null;

		if (uriObj instanceof URI uri)
			return uri;

		try {
			if (uriObj instanceof URL url)
				return url.toURI();

			if (uriObj instanceof String string) {
				try {
					URL url = new URL(string);
					return url.toURI();
				} catch (MalformedURLException mfue) {
					// Ignore
				}

				File f = new File(string);
				if (f.isFile()) {
					return f.toURI();
				}
				return new URI(string);
			}

		} catch (URISyntaxException e) {
			throw new IllegalArgumentException("Resource content capability has illegal URL attribute", e);
		}

		return null;
	}

	public static String getVersionAttributeForNamespace(String namespace) {
		return switch (namespace) {
			case LibraryNamespace.NAMESPACE, "bnd.info" -> "version";
			case IdentityNamespace.IDENTITY_NAMESPACE -> IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
			case BundleNamespace.BUNDLE_NAMESPACE, HostNamespace.HOST_NAMESPACE -> AbstractWiringNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE;
			case PackageNamespace.PACKAGE_NAMESPACE -> PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE;
			case ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE -> ExecutionEnvironmentNamespace.CAPABILITY_VERSION_ATTRIBUTE;
			case NativeNamespace.NATIVE_NAMESPACE -> NativeNamespace.CAPABILITY_OSVERSION_ATTRIBUTE;
			case ExtenderNamespace.EXTENDER_NAMESPACE -> ExtenderNamespace.CAPABILITY_VERSION_ATTRIBUTE;
			case ContractNamespace.CONTRACT_NAMESPACE -> ContractNamespace.CAPABILITY_VERSION_ATTRIBUTE;
			case ImplementationNamespace.IMPLEMENTATION_NAMESPACE -> ImplementationNamespace.CAPABILITY_VERSION_ATTRIBUTE;
			case ServiceNamespace.SERVICE_NAMESPACE -> "version";
			default -> "version";
		};
	}

	@SuppressWarnings("unchecked")
	public static  T as(final Capability cap, Class type) {
		return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] {
			type
		}, (target, method, args) -> {
			Class declaringClass = method.getDeclaringClass();
			if ((Capability.class == declaringClass) || (Object.class == declaringClass)) {
				return publicLookup().unreflect(method)
					.bindTo(cap)
					.invokeWithArguments(args);
			}
			return get(method, cap.getAttributes(), cap.getDirectives(), args);
		});
	}

	@SuppressWarnings("unchecked")
	public static  T as(final Requirement req, Class type) {
		return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] {
			type
		}, (target, method, args) -> {
			Class declaringClass = method.getDeclaringClass();
			if ((Requirement.class == declaringClass) || (Object.class == declaringClass)) {
				return publicLookup().unreflect(method)
					.bindTo(req)
					.invokeWithArguments(args);
			}
			return get(method, req.getAttributes(), req.getDirectives(), args);
		});
	}

	private static Object get(Method method, Map attrs, Map directives, Object[] args)
		throws Exception {
		String name = method.getName()
			.replace('_', '.');

		Object value;
		if (name.startsWith("$"))
			value = getValue(directives, name.substring(1));
		else {
			value = getValue(attrs, name);
		}
		if (value == null && args != null && args.length == 1)
			value = args[0];

		return cnv.convert(method.getGenericReturnType(), value);
	}

	private static Object getValue(Map attrs, String name) {
		Object object = attrs.get(name);
		if (object != null)
			return object;

		return attrs.get(name.replace('.', '-'));
	}

	public static Set getResources(Collection providers) {
		if (providers == null || providers.isEmpty())
			return Sets.of();

		return getResources(providers.stream());
	}

	public static Map> getIndexedByResource(Collection providers) {
		if (providers == null || providers.isEmpty())
			return Maps.of();
		return providers.stream()
			.collect(groupingBy(Capability::getResource, toCapabilities()));
	}

	private static Set getResources(Stream providers) {
		return providers.map(Capability::getResource)
			.collect(toCollection(() -> new TreeSet<>(RESOURCE_COMPARATOR)));
	}

	public static Requirement createWildcardRequirement() {
		return CapReqBuilder.createSimpleRequirement(IdentityNamespace.IDENTITY_NAMESPACE, "*", null)
			.buildSyntheticRequirement();
	}

	public static boolean isEffective(Requirement r, Capability c) {
		String capabilityEffective = c.getDirectives()
			.get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE);

		//
		// resolve on the capability will always match any
		// requirement effective
		//

		if (capabilityEffective == null) // default resolve
			return true;

		if (capabilityEffective.equals(Namespace.EFFECTIVE_RESOLVE))
			return true;

		String requirementEffective = r.getDirectives()
			.get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE);

		//
		// If requirement is resolve but capability isn't
		//

		if (requirementEffective == null)
			return false;

		return capabilityEffective.equals(requirementEffective);
	}

	public static Predicate> filterPredicate(String filterString) {
		if (filterString == null) {
			return m -> true;
		}
		try {
			Filter filter = FilterImpl.createFilter(filterString);
			return filter::matches;
		} catch (InvalidSyntaxException e) {
			return m -> false;
		}
	}

	public static boolean matches(Requirement requirement, Resource resource) {
		return capabilityStream(resource, requirement.getNamespace()).anyMatch(matcher(requirement));
	}

	public static boolean matches(Requirement requirement, Capability capability) {
		return matcher(requirement).test(capability);
	}

	public static Predicate matcher(Requirement requirement) {
		return matcher(requirement, ResourceUtils::filterPredicate);
	}

	public static Predicate matcher(Requirement requirement,
		Function>> filter) {
		Predicate matcher = capability -> Objects.equals(requirement.getNamespace(),
			capability.getNamespace()) && isEffective(requirement, capability);
		return matcher.and(filterMatcher(requirement, filter));
	}

	// Pattern to find attr names in a filter string
	private static final Pattern ATTR_NAME = Pattern.compile("\\(\\s*([^()=<>~\\s]+)\\s*[=<>~][^)]+\\)");

	public static Predicate filterMatcher(Requirement requirement) {
		return filterMatcher(requirement, ResourceUtils::filterPredicate);
	}

	public static Predicate filterMatcher(Requirement requirement,
		Function>> filter) {
		String filterDirective = requirement.getDirectives()
			.get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
		Supplier>> predicate = Memoize.supplier(filter, filterDirective);

		Predicate matcher = capability -> {
			if ((filterDirective != null) && !predicate.get()
				.test(capability.getAttributes())) {
				return false;
			}
			// Mandatory attribute matching (Core 3.7.8) for wiring namespaces
			if (capability.getNamespace()
				.startsWith("osgi.wiring.")) {
				String mandatoryDirective = capability.getDirectives()
					.get(AbstractWiringNamespace.CAPABILITY_MANDATORY_DIRECTIVE);
				if (mandatoryDirective == null) {
					return true;
				}
				if (filterDirective == null) {
					return false;
				}
				Set mandatory = Strings.splitAsStream(mandatoryDirective)
					.collect(toSet());
				for (Matcher m = ATTR_NAME.matcher(filterDirective); m.find();) {
					String attr = m.group(1);
					mandatory.remove(attr);
				}
				return mandatory.isEmpty();
			}
			return true;
		};
		return matcher;
	}

	public static String getEffective(Map directives) {
		String effective = directives.get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
		if (effective == null)
			return Namespace.EFFECTIVE_RESOLVE;
		else
			return effective;
	}

	public static ResolutionDirective getResolution(Requirement requirement) {
		String resolution = requirement.getDirectives()
			.get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
		if (resolution == null || resolution.equals(Namespace.RESOLUTION_MANDATORY))
			return ResolutionDirective.mandatory;

		if (resolution.equals(Namespace.RESOLUTION_OPTIONAL))
			return ResolutionDirective.optional;

		return null;
	}

	public static String toRequireCapability(Requirement requirement) throws Exception {
		CapReqBuilder builder = CapReqBuilder.clone(requirement);
		StringBuilder sb = new StringBuilder(builder.getNamespace()).append(';')
			.append(builder.toAttrs());
		return sb.toString();
	}

	public static String toProvideCapability(Capability capability) throws Exception {
		CapReqBuilder builder = CapReqBuilder.clone(capability);
		StringBuilder sb = new StringBuilder(builder.getNamespace()).append(';')
			.append(builder.toAttrs());
		return sb.toString();
	}

	public static Map getLocations(Resource resource) {
		Map locations = MapStream
			.of(capabilityStream(resource, ContentNamespace.CONTENT_NAMESPACE).map(asFunction(c -> {
				Map attributes = c.getAttributes();
				Object u = attributes.get(ContentNamespace.CAPABILITY_URL_ATTRIBUTE);
				if (u == null) {
					return null;
				}
				URI uri = URI.create(u.toString());
				Object osgi_content;
				if (attributes instanceof DeferredValueMap) {
					DeferredValueMap deferredMap = (DeferredValueMap) attributes;
					Object value = deferredMap.getDeferred(ContentNamespace.CONTENT_NAMESPACE);
					if (value instanceof DeferredValue) {
						@SuppressWarnings("unchecked")
						DeferredValue deferred = (DeferredValue) value;
						if (String.class.isAssignableFrom(deferred.type())) {
							osgi_content = deferred;
						} else {
							osgi_content = cnv.convert(String.class, deferred.get());
						}
					} else {
						osgi_content = cnv.convert(String.class, value);
					}
				} else {
					osgi_content = cnv.convert(String.class, attributes.get(ContentNamespace.CONTENT_NAMESPACE));
				}
				return MapStream.entry(uri, osgi_content);
			}))
				.filter(Objects::nonNull))
			// We can't use MapStream.toMap since we must handle null
			// osgi_content values.
			// .collect(MapStream.toMap());
			.collect(Collector.of(HashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()),
				(m1, m2) -> {
					m1.putAll(m2);
					return m1;
				}));
		@SuppressWarnings({
			"rawtypes", "unchecked"
		})
		Map result = new DeferredValueMap(locations);
		return result;
	}

	public static List findProviders(Requirement requirement,
		Collection capabilities) {
		return capabilities.stream()
			.filter(matcher(requirement))
			.collect(toCapabilities());
	}

	public static boolean isFragment(Resource resource) {
		IdentityCapability identity = getIdentityCapability(resource);
		if (identity == null)
			return false;
		return IdentityNamespace.TYPE_FRAGMENT.equals(identity.getAttributes()
			.get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE));
	}

	public static String stripDirective(String name) {
		if (Strings.charAt(name, -1) == ':')
			return Strings.substring(name, 0, -1);
		return name;
	}

	public static String getIdentity(Capability identityCapability) throws IllegalArgumentException {
		String id = (String) identityCapability.getAttributes()
			.get(IdentityNamespace.IDENTITY_NAMESPACE);
		if (id == null)
			throw new IllegalArgumentException("Resource identity capability has missing identity attribute");
		return id;
	}

	public static String getIdentity(Resource resource) throws IllegalArgumentException {
		return getIdentity(getIdentityCapability(resource));
	}

	public static Version getVersion(Resource resource) throws IllegalArgumentException {
		return getVersion(getIdentityCapability(resource));
	}

	/**
	 * Create a VersionedClause by applying a version range mask to the
	 * resource! Masks are defined by
	 * {@link aQute.bnd.osgi.Macro#_range(String[])}. If the resource should
	 * represent a project in the bnd workspace, then instead the VersionClause
	 * will refer to it as a snapshot version: e.g. ;version=snapshot
	 */
	public static VersionedClause toVersionClause(Resource resource) {
		return toVersionClause(resource, "[===,==+)");
	}

	public static VersionedClause toVersionClause(Resource resource, String mask) {
		Capability idCap = getIdentityCapability(resource);
		String identity = getIdentity(idCap);
		String versionString;
		if (resource.getCapabilities(WORKSPACE_NAMESPACE)
			.isEmpty()) {
			Macro macro = new Macro(new Processor());
			Version version = getVersion(idCap);
			versionString = macro._range(new String[] {
				"range", mask, version.toString()
			});
		} else {
			versionString = "snapshot";
		}
		Attrs attribs = new Attrs();
		attribs.put(Constants.VERSION_ATTRIBUTE, versionString);
		return new VersionedClause(identity, attribs);
	}

	public static List toVersionedClauses(Collection resources) {
		List runBundles = resources.stream()
			.map(ResourceUtils::toVersionClause)
			.distinct()
			.collect(toList());
		return runBundles;
	}

	private final static Collection all = Lists.of(createWildcardRequirement());

	/**
	 * Return all resources from a repository as returned by the wildcard
	 * requirement, see {@link #createWildcardRequirement()}
	 *
	 * @param repository the repository to use
	 * @return a set of resources from the repository.
	 */
	public static Set getAllResources(Repository repository) {
		return getResources(repository.findProviders(all)
			.values()
			.stream()
			.flatMap(Collection::stream));
	}

	@SuppressWarnings({
		"rawtypes", "unchecked"
	})
	public static final Comparator nullsFirst = Comparator.nullsFirst(Comparator.naturalOrder());

	/**
	 * Compare two resources. This can be used to act as a comparator. The
	 * comparison is first done on name and then version.
	 *
	 * @param a the left resource
	 * @param b the right resource
	 * @return 0 if equal bame and version, 1 if left has a higher name or same
	 *         name and higher version, -1 otherwise
	 */
	public static int compareTo(Resource a, Resource b) {
		int n = comparePresent(a, b);
		if (isFinal(n))
			return n;

		IdentityCapability ia = ResourceUtils.getIdentityCapability(a);
		IdentityCapability ib = ResourceUtils.getIdentityCapability(b);

		int compare = compare(ia, ib);
		if (compare != 0)
			return compare;

		compare = compare(ia.osgi_identity(), ib.osgi_identity());
		if (compare != 0)
			return compare;

		return compare(ia.version(), ib.version());
	}

	/**
	 * Compare two capabilities. The order is:
	 * 
    *
  • nulls last *
  • namespace lexical *
  • namespace specific as described in its Namespace. *
  • by "version" if unknown namespace *
  • compare resources *
* * @param a the left capability * @param b the right capability * @return -1 if right is higher, 0 if equal, and 1 if left is higher */ public static int compareTo(Capability a, Capability b) { int compare = compare(a, b); if (compare != 0) return compare; assert a != null && b != null; String nsa = a.getNamespace(); String nsb = b.getNamespace(); compare = compare(nsa, nsb); if (compare != 0) return compare; assert nsa.equals(nsb); Map aa = a.getAttributes(); Map ab = b.getAttributes(); compare = comparePresent(aa, ab); if (Comparators.isFinal(compare)) return compare; assert aa != null && ab != null; compare = switch (nsa) { case BundleNamespace.BUNDLE_NAMESPACE -> compareMapsByKeys(aa, ab, // BundleNamespace.BUNDLE_NAMESPACE, // BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); case HostNamespace.HOST_NAMESPACE -> compareMapsByKeys(aa, ab, // HostNamespace.HOST_NAMESPACE, // HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); case ServiceNamespace.SERVICE_NAMESPACE -> compareMapsByKeys(aa, ab, ServiceNamespace.CAPABILITY_OBJECTCLASS_ATTRIBUTE, "version"); case PackageNamespace.PACKAGE_NAMESPACE -> compareMapsByKeys(aa, ab, // PackageNamespace.PACKAGE_NAMESPACE, // PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, // PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE, // PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); default -> compareMapsByKeys(aa, ab, nsa, "version"); }; if (compare != 0) return compare; if (aa.size() > ab.size()) return 1; if (aa.size() < ab.size()) return -1; return compareTo(a.getResource(), b.getResource()); } public static int compareTo(Requirement a, Requirement b) { int compare = compare(a, b); if (compare != 0) return compare; String ns1 = a.getNamespace(); String ns2 = b.getNamespace(); compare = compare(ns1, ns2); if (compare != 0) return compare; String f1 = a.getDirectives() .get("filter"); String f2 = b.getDirectives() .get("filter"); return compare(f1, f2); } public static List sort(Collection resources) { List list = resources.stream() .sorted(ResourceUtils::compareTo) .collect(toList()); return list; } /** * Sort the resources by symbolic name and version * * @param resources the set of resources to sort * @return a sorted set of resources */ public static List sortByNameVersion(Collection resources) { List list = resources.stream() .sorted(ResourceUtils::compareTo) .collect(toList()); return list; } public static boolean isInitialRequirement(Resource resource) { IdentityCapability identityCapability = getIdentityCapability(resource); if (identityCapability == null) return false; String osgi_identity = identityCapability.osgi_identity(); if (osgi_identity == null) return false; return Constants.IDENTITY_INITIAL_RESOURCE.equals(osgi_identity); } public static Collector, List> toCapabilities() { return Collector.of(ArrayList::new, ResourceUtils::capabilitiesAccumulator, ResourceUtils::capabilitiesCombiner); } public static > void capabilitiesAccumulator( COLLECTION collection, CAPABILITY capability) { if (!collection.contains(capability)) { collection.add(capability); } } public static > COLLECTION capabilitiesCombiner( COLLECTION leftCollection, COLLECTION rightCollection) { if (leftCollection.isEmpty()) { return rightCollection; } rightCollection.forEach(capability -> capabilitiesAccumulator(leftCollection, capability)); return leftCollection; } public static > Map> findProviders( Collection requirements, Function provider) { @SuppressWarnings("unchecked") Map> result = (Map>) requirements .stream() .collect(toMap(Function.identity(), provider, ResourceUtils::capabilitiesCombiner)); return result; } public static Map> emptyProviders( Collection requirements) { return findProviders(requirements, requirement -> new ArrayList<>()); } /** * Return the type field in the Identity capability. The IdentityCapability * interface has the problem that for the type it returns an enum, which * makes it impossible to check for custom identity types. * * @param r the resource * @return the type string */ public static Optional getType(Resource r) { List id = r.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE); if (!id.isEmpty()) { Object object = id.get(0) .getAttributes() .get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE); if (object instanceof String s) { return Optional.of(s); } } return Optional.empty(); } /** * Return if the current IdentityNamespace type of the given resource r is * in the given list of types. */ public static boolean hasType(Resource r, String... types) { return getType(r).map(s -> Strings.in(types, s)) .orElse(false); } /** * Select the capabilities that match the namespace and the filter applied * to the attributes. * * @param resource the resource to search * @param namespace the namespace to search or null * @param filter the filter to apply * @return matching capabilities */ public static List findCapability(Resource resource, String namespace, String filter) { Predicate> filterPredicate = filterPredicate(filter); return resource.getCapabilities(namespace) .stream() .filter(c -> filterPredicate.test(c.getAttributes())) .toList(); } /** * Detect capabilities containing packages that have the same name but * differ in the contained classes. Since the "bnd.hashes" attribute * contains the hashes of each class, we can detect differences. A problem * is when there are two bundles exporting the same package BUT with * different content (e.g. different files). This can lead to the problem * that a consumer expects a certain class in a package of bundleA but it is * not there - instead it is in bundleB. * * @param primaryAttributeName E.g. for a package it is * "osgi.wiring.package" * @param capabilities list of capabilities to filter * @return list of problematic capabilities */ public static List detectDuplicateCapabilitiesWithDifferentHashes(String primaryAttributeName, List capabilities) { Map> groupedByPackage = capabilities.stream() .filter(cap -> cap.getAttributes() .containsKey(primaryAttributeName)) .collect(groupingBy(cap -> (String) cap.getAttributes() .get(primaryAttributeName), Lists.toList())); List culprits = new ArrayList<>(); for (List caps : groupedByPackage.values()) { if (caps.size() > 1) { // Compare hashes of each capability Set> detectDifferences = new HashSet<>(); for (Capability cap : caps) { @SuppressWarnings("unchecked") List hashes = (List) cap.getAttributes() .get("bnd.hashes"); if (detectDifferences.size() == 0) { detectDifferences.add(hashes); } else if (detectDifferences.size() > 0 && detectDifferences.add(hashes)) { // being here means we were able to add a different // bnd.hashes for the same primaryAttributeName (e.g. // "osgi.wiring.package"). // this is the problem we want to detect. // mark all capabilities for this primaryAttributeName // as "problematic" // this means there maybe multiple exporters of a // package // but with different content (classes) culprits.addAll(caps); break; } } } } return culprits; } /** * Calculates a list of packages which are exported and also imported. This * can be handy for debugging and identify unwanted substitution packages / * self-imports. * * @param r the resource * @return a non-null list of packages. */ public static Collection getSubstitutionPackages(Resource r) { List caps = r.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE); List requirements = r.getRequirements(PackageNamespace.PACKAGE_NAMESPACE); Set exportedPackages = new LinkedHashSet<>(); Set importedPackages = new LinkedHashSet<>(); // Populate exported packages for (Capability cap : caps) { String packageName = (String) cap.getAttributes() .get(PackageNamespace.PACKAGE_NAMESPACE); if (packageName != null) { exportedPackages.add(packageName); } } // Populate imported packages Map pckVersionMap = new LinkedHashMap<>(); FilterParser fp = new FilterParser(); // new instance required to avoid // invorrect caching for (Requirement req : requirements) { PackageExpression pckExp = getRequirementPackage(req, fp); if (pckExp != null) { importedPackages.add(pckExp.getPackageName()); pckVersionMap.put(pckExp.getPackageName(), pckExp); } } Set substitutions = new LinkedHashSet<>(exportedPackages); substitutions.retainAll(importedPackages); return substitutions.stream() .map(pck -> pckVersionMap.get(pck)) .collect(Collectors.toCollection(LinkedHashSet::new)); } private static PackageExpression getRequirementPackage(Requirement req, FilterParser fp) { Expression exp = fp.parse(req); if (exp instanceof PackageExpression pckExp) { return pckExp; } return null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy