
aQute.bnd.osgi.resource.CapReqBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of biz.aQute.bndlib Show documentation
Show all versions of biz.aQute.bndlib Show documentation
bndlib: A Swiss Army Knife for OSGi
The newest version!
package aQute.bnd.osgi.resource;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
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.PackageNamespace;
import org.osgi.namespace.contract.ContractNamespace;
import org.osgi.namespace.extender.ExtenderNamespace;
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 aQute.bnd.exceptions.Exceptions;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Processor;
import aQute.bnd.stream.MapStream;
import aQute.bnd.version.VersionRange;
import aQute.lib.converter.Converter;
public class CapReqBuilder {
private static final String REQ_ALIAS_IDENTITY = "bnd.identity";
private static final String REQ_ALIAS_IDENTITY_NAME_ATTRIB = "id";
private static final String REQ_ALIAS_IDENTITY_VERSION_ATTRIB = "version";
private static final String REQ_ALIAS_LITERAL = "bnd.literal";
private static final String REQ_ALIAS_LITERAL_ATTRIB = REQ_ALIAS_LITERAL;
private final String namespace;
private Resource resource;
private final Map attributes = new HashMap<>();
private final Map directives = new HashMap<>();
public CapReqBuilder(String namespace) {
this.namespace = requireNonNull(namespace);
}
public CapReqBuilder(String namespace, Attrs attrs) {
this(namespace);
addAttributesOrDirectives(attrs);
}
public CapReqBuilder(Resource resource, String namespace) {
this(namespace);
setResource(resource);
}
public static CapReqBuilder clone(Capability capability) {
return new CapReqBuilder(capability.getNamespace()).from(capability);
}
public static CapReqBuilder clone(Requirement requirement) {
return new CapReqBuilder(requirement.getNamespace()).from(requirement);
}
public String getNamespace() {
return namespace;
}
public Resource getResource() {
return resource;
}
public CapReqBuilder setResource(Resource resource) {
this.resource = resource;
return this;
}
public CapReqBuilder addAttribute(String name, Object value) {
if (value == null)
return this;
if (value.getClass()
.isArray()) {
try {
value = Converter.cnv(List.class, value);
} catch (Exception e) {
throw Exceptions.duck(e);
}
}
if (isVersion(value, aQute.bnd.version.Version.class)
|| name.equals(ResourceUtils.getVersionAttributeForNamespace(getNamespace()))) {
value = toVersions(value);
}
attributes.put(name, value);
return this;
}
public boolean hasAttribute(String name) {
return attributes.containsKey(name);
}
public CapReqBuilder removeAttribute(String name) {
attributes.remove(name);
return this;
}
public boolean isVersion(Object value) {
return isVersion(value, Version.class);
}
private boolean isVersion(Object value, Class> versionClass) {
if (versionClass.isInstance(value)) {
return true;
}
if (value instanceof Collection> v) {
if (v.isEmpty()) {
return false;
}
return isVersion(v.iterator()
.next(), versionClass);
}
if (value.getClass()
.isArray()) {
if (versionClass.isAssignableFrom(value.getClass()
.getComponentType())) {
return true;
}
if (Array.getLength(value) == 0) {
return false;
}
return isVersion(Array.get(value, 0), versionClass);
}
return false;
}
public CapReqBuilder addAttributes(Map extends String, ? extends Object> attributes) {
for (Entry extends String, ? extends Object> entry : attributes.entrySet()) {
if (entry instanceof DeferredValueEntry extends String, ? extends Object> deferred) {
addAttribute(deferred.getKey(), deferred.getDeferredValue());
} else {
addAttribute(entry.getKey(), entry.getValue());
}
}
return this;
}
public CapReqBuilder addDirective(String name, String value) {
if (value == null)
return this;
directives.put(ResourceUtils.stripDirective(name), value);
return this;
}
public boolean hasDirective(String name) {
return directives.containsKey(ResourceUtils.stripDirective(name));
}
public CapReqBuilder removeDirective(String name) {
directives.remove(ResourceUtils.stripDirective(name));
return this;
}
public CapReqBuilder addDirectives(Attrs directives) {
directives.stream()
.mapKey(Attrs::toDirective)
.filterKey(Objects::nonNull)
.forEachOrdered(this::addDirective);
return this;
}
public CapReqBuilder addDirectives(Map directives) {
directives.forEach(this::addDirective);
return this;
}
public CapabilityImpl buildCapability() {
if (resource == null)
throw new IllegalStateException("Cannot build Capability with null Resource.");
return new CapabilityImpl(namespace, resource, directives, attributes);
}
public CapabilityImpl buildSyntheticCapability() {
return new CapabilityImpl(namespace, null, directives, attributes);
}
public RequirementImpl buildRequirement() {
if (resource == null)
throw new IllegalStateException(
"Cannot build Requirement with null Resource. use buildSyntheticRequirement");
return new RequirementImpl(namespace, resource, directives, attributes);
}
public RequirementImpl buildSyntheticRequirement() {
return new RequirementImpl(namespace, null, directives, attributes);
}
public static CapReqBuilder createPackageRequirement(String name, String versionRange) {
return createPackageRequirement(name, null, versionRange);
}
public static RequirementBuilder createPackageRequirement(String name, Attrs attrs, String versionRange) {
RequirementBuilder builder = new RequirementBuilder(PackageNamespace.PACKAGE_NAMESPACE);
if (attrs != null) {
builder.addDirectives(attrs);
if (versionRange == null) {
versionRange = attrs.get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
}
}
builder.addFilter(PackageNamespace.PACKAGE_NAMESPACE, name, versionRange, attrs);
// Add package name as an attribute to help other parts of Bnd find the
// name of the required package without having to grovel through the
// filter string.
builder.addAttribute(PackageNamespace.PACKAGE_NAMESPACE, name);
return builder;
}
public static CapabilityBuilder createPackageCapability(String name, Attrs attrs, String bundle_symbolic_name,
Version bundle_version) {
CapabilityBuilder builder = new CapabilityBuilder(PackageNamespace.PACKAGE_NAMESPACE);
if (attrs != null) {
builder.addAttributesOrDirectives(attrs);
}
if (!builder.hasAttribute(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE)) {
builder.addAttribute(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, Version.emptyVersion);
}
builder.addAttribute(PackageNamespace.PACKAGE_NAMESPACE, requireNonNull(name));
if (bundle_symbolic_name != null) {
builder.addAttribute(PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE, bundle_symbolic_name);
}
if (bundle_version != null) {
builder.addAttribute(AbstractWiringNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, bundle_version);
}
return builder;
}
public static CapReqBuilder createBundleRequirement(String name, String versionRange) {
return createSimpleRequirement(IdentityNamespace.IDENTITY_NAMESPACE, name, versionRange);
}
public static CapReqBuilder createSimpleRequirement(String namespace, String name, String versionRange) {
RequirementBuilder builder = new RequirementBuilder(namespace);
builder.addFilter(namespace, name, versionRange, null);
return builder;
}
public static RequirementBuilder createServiceRequirement(String name) {
RequirementBuilder builder = new RequirementBuilder(ServiceNamespace.SERVICE_NAMESPACE);
builder.addFilter(ServiceNamespace.CAPABILITY_OBJECTCLASS_ATTRIBUTE, name, null, null);
return builder;
}
public CharSequence and(Object... exprs) {
StringBuilder sb = new StringBuilder().append('(')
.append('&');
for (Object expr : exprs) {
sb.append(toFilter(expr));
}
return sb.append(')');
}
public CharSequence or(Object... exprs) {
StringBuilder sb = new StringBuilder().append('(')
.append('|');
for (Object expr : exprs) {
sb.append(toFilter(expr));
}
return sb.append(')');
}
public CharSequence not(Object expr) {
StringBuilder sb = new StringBuilder().append('(')
.append('!');
sb.append(toFilter(expr));
return sb.append(')');
}
private CharSequence toFilter(Object expr) {
if (expr instanceof CharSequence charSequence) {
return charSequence;
}
if (expr instanceof VersionRange versionRange) {
return versionRange.toFilter();
}
return expr.toString();
}
public CapReqBuilder filter(CharSequence f) {
return addDirective(Namespace.REQUIREMENT_FILTER_DIRECTIVE, f.toString());
}
/**
* Equivalent to {@code getRequirementsFrom(rr, true)}.
*
* @param rr
*/
public static List getRequirementsFrom(Parameters rr) {
return getRequirementsFrom(rr, true);
}
/**
* Parse requirements from a Parameters set in the form of an OSGi
* Require-Capability header.
*
* @param rr The Require-Capability header.
* @param unalias Whether to unalias requirements. If false then an aliases
* such as "bundle; bsn=org.foo" will be returned as a raw
* Requirement in the unspecified namespace "bundle".
* @return The list of parsed requirements.
*/
public static List getRequirementsFrom(Parameters rr, boolean unalias) {
List requirements = rr.stream()
.mapToObj((k, v) -> getRequirementFrom(Processor.removeDuplicateMarker(k), v, unalias))
.collect(toList());
return requirements;
}
public static Requirement getRequirementFrom(String namespace, Attrs attrs) {
return getRequirementFrom(namespace, attrs, true);
}
public static Requirement getRequirementFrom(String namespace, Attrs attrs, boolean unalias) {
CapReqBuilder builder = createCapReqBuilder(namespace, attrs);
Requirement requirement = builder.buildSyntheticRequirement();
if (unalias)
requirement = unalias(requirement);
return requirement;
}
public static CapReqBuilder createCapReqBuilder(String namespace, Attrs attrs) {
CapReqBuilder builder = new CapReqBuilder(namespace);
if (attrs != null) {
builder.addAttributesOrDirectives(attrs);
}
return builder;
}
/**
* Convert an alias requirement to its canonical form. For example:
* "bnd.identity; id=org.example; version='[1.0,2.0)'
" will be
* converted to
* "osgi.identity; filter:='(&(osgi.identity=org.example)(version>=1.0)(!(version>=2.0)))'
"
* Requirements that are not recognized as aliases will be returned
* unchanged.
*/
public static Requirement unalias(Requirement requirement) {
if (requirement == null) {
return null;
}
switch (requirement.getNamespace()) {
case REQ_ALIAS_LITERAL : {
String namespace = Objects.toString(requirement.getAttributes()
.get(REQ_ALIAS_LITERAL_ATTRIB), null);
if (namespace == null) {
throw new IllegalArgumentException(
String.format("Requirement alias %s is missing mandatory attribute '%s' of type String",
REQ_ALIAS_LITERAL, REQ_ALIAS_LITERAL_ATTRIB));
}
CapReqBuilder builder = new CapReqBuilder(namespace).from(requirement)
.removeAttribute(REQ_ALIAS_LITERAL_ATTRIB);
return builder.buildSyntheticRequirement();
}
case REQ_ALIAS_IDENTITY : {
String name = Objects.toString(requirement.getAttributes()
.get(REQ_ALIAS_IDENTITY_NAME_ATTRIB), null);
String versionRange = Objects.toString(requirement.getAttributes()
.get(REQ_ALIAS_IDENTITY_VERSION_ATTRIB), null);
if (name == null) {
throw new IllegalArgumentException(
String.format("Requirement alias '%s' is missing mandatory attribute '%s' of type String",
REQ_ALIAS_IDENTITY, REQ_ALIAS_IDENTITY_NAME_ATTRIB));
}
CapReqBuilder builder = new CapReqBuilder(IdentityNamespace.IDENTITY_NAMESPACE).from(requirement)
.removeAttribute(REQ_ALIAS_IDENTITY_NAME_ATTRIB)
.removeAttribute(REQ_ALIAS_IDENTITY_VERSION_ATTRIB);
builder.addFilter(IdentityNamespace.IDENTITY_NAMESPACE, name, versionRange, null);
return builder.buildSyntheticRequirement();
}
default : {
return requirement;
}
}
}
public static List getCapabilitiesFrom(Parameters rr) {
List capabilities = rr.stream()
.mapToObj((k, v) -> getCapabilityFrom(Processor.removeDuplicateMarker(k), v))
.collect(toList());
return capabilities;
}
public static Capability getCapabilityFrom(String namespace, Attrs attrs) {
return createCapReqBuilder(namespace, attrs).buildSyntheticCapability();
}
public CapReqBuilder from(Capability capability) {
return addAttributes(capability.getAttributes()).addDirectives(capability.getDirectives());
}
public CapReqBuilder from(Requirement requirement) {
return addAttributes(requirement.getAttributes()).addDirectives(requirement.getDirectives());
}
public static Capability copy(Capability capability, Resource resource) {
CapReqBuilder clone = clone(capability);
return (resource != null) ? clone.setResource(resource)
.buildCapability() : clone.buildSyntheticCapability();
}
public static Requirement copy(Requirement requirement, Resource resource) {
CapReqBuilder clone = clone(requirement);
return (resource != null) ? clone.setResource(resource)
.buildRequirement() : clone.buildSyntheticRequirement();
}
/**
* In bnd, we use one map for both directives & attributes. This method will
* properly dispatch them AND take care of typing
*
* @param attrs
*/
public void addAttributesOrDirectives(Attrs attrs) {
for (Entry e : attrs.entrySet()) {
String name = e.getKey();
String directive = Attrs.toDirective(name);
if (directive != null) {
addDirective(directive, e.getValue());
} else {
Object typed = attrs.getTyped(name);
addAttribute(name, typed);
}
}
}
/**
* In bnd, we use one map for both directives & attributes. This method will
* ignore directives.
*
* @param attrs
*/
public CapReqBuilder addAttributes(Attrs attrs) {
attrs.keySet()
.stream()
.filter(Attrs::isAttribute)
.forEachOrdered(name -> {
Object typed = attrs.getTyped(name);
addAttribute(name, typed);
});
return this;
}
public void addFilter(String nameAttr, String name, String versionRange, Attrs attrs) {
StringBuilder filter = new StringBuilder(256).append('(')
.append('&')
.append('(')
.append(requireNonNull(nameAttr))
.append('=')
.append(requireNonNull(name))
.append(')');
final int len = filter.length();
final String versionAttrName = Optional
.ofNullable(ResourceUtils.getVersionAttributeForNamespace(getNamespace()))
.orElse(Constants.VERSION_ATTRIBUTE);
appendFilterVersionRange(filter, versionAttrName, versionRange);
final boolean versionAttrFilterAdded = filter.length() > len;
// Attribute matching (Core 3.7.7) for wiring namespaces
if (getNamespace().startsWith("osgi.wiring.")) {
MapStream attrStream = MapStream.ofNullable(attrs)
.filterKey(Attrs::isAttribute);
if (versionAttrFilterAdded) {
attrStream = attrStream.filterKey(attrName -> !attrName.equals(versionAttrName));
}
attrStream.sortedByKey()
.forEachOrdered((attrName, attrValue) -> {
switch (attrName) {
case PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE :
case AbstractWiringNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE :
appendFilterVersionRange(filter, attrName, attrValue);
break;
default :
appendFilterEquals(filter, attrName, attrValue);
break;
}
});
}
String value = (filter.length() > len) ? filter.append(')')
.toString() : filter.substring(2);
addDirective(Namespace.REQUIREMENT_FILTER_DIRECTIVE, value);
}
private static void appendFilterEquals(StringBuilder filter, String name, String value) {
if (value != null) {
filter.append('(')
.append(name)
.append('=');
escapeFilterValue(filter, value).append(')');
}
}
private static void appendFilterVersionRange(StringBuilder filter, String name, String versionRange) {
Optional.ofNullable(versionRange)
.filter(VersionRange::isOSGiVersionRange)
.map(VersionRange::parseOSGiVersionRange)
.map(range -> range.toFilter(name))
.ifPresent(range -> {
if ((filter.length() > 1) && (filter.charAt(1) == '&') && (range.length() > 1)
&& (range.charAt(1) == '&')) {
filter.append(range, 2, range.length() - 1);
} else {
filter.append(range);
}
});
}
/**
* If value must contain one of the characters reverse solidus ('\' \u005C),
* asterisk ('*' \u002A), parentheses open ('(' \u0028) or parentheses close
* (')' \u0029), then these characters should be preceded with the reverse
* solidus ('\' \u005C) character. Spaces are significant in value. Space
* characters are defined by Character.isWhiteSpace().
*/
public static String escapeFilterValue(String value) {
final int len = value.length();
StringBuilder sb = escapeFilterValue(new StringBuilder(len), value);
return (len == sb.length()) ? value : sb.toString();
}
private static StringBuilder escapeFilterValue(StringBuilder sb, String value) {
final int len = value.length();
for (int i = 0; i < len; i++) {
char c = value.charAt(i);
switch (c) {
case '\\' :
case '*' :
case '(' :
case ')' :
sb.append('\\')
.append(c);
break;
default :
sb.append(c);
break;
}
}
return sb;
}
public void and(String... s) {
StringBuilder filter = new StringBuilder().append('(')
.append('&');
String previous = directives.get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
if (previous != null) {
filter.append(previous);
}
for (String subexpr : s) {
filter.append(subexpr);
}
filter.append(')');
addDirective(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
}
public boolean isPackage() {
return PackageNamespace.PACKAGE_NAMESPACE.equals(getNamespace());
}
public boolean isHost() {
return HostNamespace.HOST_NAMESPACE.equals(getNamespace());
}
public boolean isBundle() {
return BundleNamespace.BUNDLE_NAMESPACE.equals(getNamespace());
}
public boolean isService() {
return ServiceNamespace.SERVICE_NAMESPACE.equals(getNamespace());
}
public boolean isContract() {
return ContractNamespace.CONTRACT_NAMESPACE.equals(getNamespace());
}
public boolean isIdentity() {
return IdentityNamespace.IDENTITY_NAMESPACE.equals(getNamespace());
}
public boolean isContent() {
return ContentNamespace.CONTENT_NAMESPACE.equals(getNamespace());
}
public boolean isEE() {
return ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(getNamespace());
}
public boolean isExtender() {
return ExtenderNamespace.EXTENDER_NAMESPACE.equals(getNamespace());
}
public Attrs toAttrs() {
String versionAttrName = Optional.ofNullable(ResourceUtils.getVersionAttributeForNamespace(getNamespace()))
.orElse(Constants.VERSION_ATTRIBUTE);
Attrs attrs = new Attrs();
attributes.forEach((key, value) -> attrs.putTyped(key,
(key.equals(versionAttrName) || (value instanceof Version)) ? toBndVersions(value) : value));
directives.forEach((key, value) -> attrs.put(key.concat(":"), value));
return attrs;
}
private Object toBndVersions(Object value) {
if (value instanceof aQute.bnd.version.Version version)
return version;
if (value instanceof Version osgiVersion) {
return new aQute.bnd.version.Version(osgiVersion.getMajor(), osgiVersion.getMinor(), osgiVersion.getMicro(),
osgiVersion.getQualifier());
}
if (value instanceof String versionString)
return new aQute.bnd.version.Version(versionString);
if (value instanceof Number versionNumber)
try {
return new aQute.bnd.version.Version(versionNumber.intValue());
} catch (Exception e) {
return value;
}
if (value instanceof Collection> v) {
if (v.isEmpty())
return value;
if (v.iterator()
.next() instanceof aQute.bnd.version.Version version)
return version;
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy