Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
aQute.bnd.osgi.resource.ResourceUtils Maven / Gradle / Ivy
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 super Resource> 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 super Resource> 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 extends Capability> providers) {
if (providers == null || providers.isEmpty())
return Sets.of();
return getResources(providers.stream());
}
public static Map> getIndexedByResource(Collection extends Capability> providers) {
if (providers == null || providers.isEmpty())
return Maps.of();
return providers.stream()
.collect(groupingBy(Capability::getResource, toCapabilities()));
}
private static Set getResources(Stream extends Capability> 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 extends Capability> 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 extends Requirement> requirements, Function super Requirement, COLLECTION> provider) {
@SuppressWarnings("unchecked")
Map> result = (Map>) requirements
.stream()
.collect(toMap(Function.identity(), provider, ResourceUtils::capabilitiesCombiner));
return result;
}
public static Map> emptyProviders(
Collection extends Requirement> 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;
}
}