package bndtools.tasks; import java.io.File; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.jar.Attributes; import java.util.jar.Manifest; import org.bndtools.utils.osgi.BundleUtils; import org.osgi.framework.Constants; import org.osgi.framework.namespace.BundleNamespace; import org.osgi.framework.namespace.HostNamespace; import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.framework.namespace.PackageNamespace; import org.osgi.resource.Capability; import org.osgi.resource.Namespace; import aQute.bnd.header.Attrs; import aQute.bnd.header.Parameters; import aQute.bnd.osgi.Builder; import aQute.bnd.osgi.Clazz; import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.Processor; import aQute.bnd.osgi.resource.CapReqBuilder; import aQute.bnd.version.VersionRange; import aQute.libg.filters.AndFilter; import aQute.libg.filters.Filter; import aQute.libg.filters.NotFilter; import aQute.libg.filters.Operator; import aQute.libg.filters.SimpleFilter; import bndtools.model.resolution.RequirementWrapper; public abstract class BndBuilderCapReqLoader implements CapReqLoader { protected final File file; public BndBuilderCapReqLoader(File file) { this.file = file; } @Override public String getShortLabel() { return file.getName(); } @Override public String getLongLabel() { return file.getName() + " - " + file.getParentFile().getAbsolutePath(); } protected abstract Builder getBuilder() throws Exception; @Override public Map<String,List<Capability>> loadCapabilities() throws Exception { Builder builder = getBuilder(); if (builder == null) return Collections.emptyMap(); Jar jar = builder.getJar(); if (jar == null) return Collections.emptyMap(); Manifest manifest = jar.getManifest(); if (manifest == null) return Collections.emptyMap(); Attributes attribs = manifest.getMainAttributes(); Map<String,List<Capability>> capMap = new HashMap<String,List<Capability>>(); // Load export packages String exportsPkgStr = attribs.getValue(Constants.EXPORT_PACKAGE); Parameters exportsMap = new Parameters(exportsPkgStr); for (Entry<String,Attrs> entry : exportsMap.entrySet()) { String pkg = Processor.removeDuplicateMarker(entry.getKey()); org.osgi.framework.Version version = org.osgi.framework.Version.parseVersion(entry.getValue().getVersion()); CapReqBuilder cb = new CapReqBuilder(PackageNamespace.PACKAGE_NAMESPACE).addAttribute(PackageNamespace.PACKAGE_NAMESPACE, pkg).addAttribute(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, version); // TODO attributes and directives addCapability(capMap, cb.buildSyntheticCapability()); } // Load identity/bundle/host String bsn = BundleUtils.getBundleSymbolicName(attribs); if (bsn != null) { // Ignore if not a bundle org.osgi.framework.Version version = org.osgi.framework.Version.parseVersion(attribs.getValue(Constants.BUNDLE_VERSION)); // TODO attributes and directives addCapability(capMap, new CapReqBuilder(IdentityNamespace.IDENTITY_NAMESPACE).addAttribute(IdentityNamespace.IDENTITY_NAMESPACE, bsn).addAttribute(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, version).buildSyntheticCapability()); addCapability(capMap, new CapReqBuilder(BundleNamespace.BUNDLE_NAMESPACE).addAttribute(BundleNamespace.BUNDLE_NAMESPACE, bsn).addAttribute(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, version).buildSyntheticCapability()); addCapability(capMap, new CapReqBuilder(HostNamespace.HOST_NAMESPACE).addAttribute(HostNamespace.HOST_NAMESPACE, bsn).addAttribute(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, version).buildSyntheticCapability()); } // Generic capabilities String providesStr = attribs.getValue(Constants.PROVIDE_CAPABILITY); Parameters provides = new Parameters(providesStr); for (Entry<String,Attrs> entry : provides.entrySet()) { String ns = Processor.removeDuplicateMarker(entry.getKey()); Attrs attrs = entry.getValue(); CapReqBuilder cb = new CapReqBuilder(ns); for (String key : attrs.keySet()) { if (key.endsWith(":")) cb.addDirective(key.substring(0, key.length() - 1), attrs.get(key)); else cb.addAttribute(key, attrs.getTyped(key)); } addCapability(capMap, cb.buildSyntheticCapability()); } return capMap; } @Override public Map<String,List<RequirementWrapper>> loadRequirements() throws Exception { Builder builder = getBuilder(); if (builder == null) return Collections.emptyMap(); Jar jar = builder.getJar(); if (jar == null) return Collections.emptyMap(); Manifest manifest = jar.getManifest(); if (manifest == null) return Collections.emptyMap(); Attributes attribs = manifest.getMainAttributes(); Map<String,List<RequirementWrapper>> requirements = new HashMap<String,List<RequirementWrapper>>(); // Process imports String importPkgStr = attribs.getValue(Constants.IMPORT_PACKAGE); Parameters importsMap = new Parameters(importPkgStr); for (Entry<String,Attrs> entry : importsMap.entrySet()) { String pkgName = Processor.removeDuplicateMarker(entry.getKey()); Attrs attrs = entry.getValue(); CapReqBuilder rb = new CapReqBuilder(PackageNamespace.PACKAGE_NAMESPACE); String filter = createVersionFilter(PackageNamespace.PACKAGE_NAMESPACE, pkgName, attrs.get(Constants.VERSION_ATTRIBUTE), PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE); rb.addDirective(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter); if (Constants.RESOLUTION_OPTIONAL.equals(attrs.get(Constants.RESOLUTION_DIRECTIVE + ":"))) rb.addDirective(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE, Namespace.RESOLUTION_OPTIONAL); Collection<Clazz> importers = findImportingClasses(pkgName, builder); RequirementWrapper rw = new RequirementWrapper(); rw.requirement = rb.buildSyntheticRequirement(); rw.requirers = importers; addRequirement(requirements, rw); } // Process require-bundle String requireBundleStr = attribs.getValue(Constants.REQUIRE_BUNDLE); Parameters requireBundles = new Parameters(requireBundleStr); for (Entry<String,Attrs> entry : requireBundles.entrySet()) { String bsn = Processor.removeDuplicateMarker(entry.getKey()); Attrs attrs = entry.getValue(); CapReqBuilder rb = new CapReqBuilder(BundleNamespace.BUNDLE_NAMESPACE); String filter = createVersionFilter(BundleNamespace.BUNDLE_NAMESPACE, bsn, attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE), BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); rb.addDirective(BundleNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter); if (Constants.RESOLUTION_OPTIONAL.equals(attrs.get(Constants.RESOLUTION_DIRECTIVE + ":"))) rb.addDirective(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE, Namespace.RESOLUTION_OPTIONAL); RequirementWrapper rw = new RequirementWrapper(); rw.requirement = rb.buildSyntheticRequirement(); addRequirement(requirements, rw); } // Process generic requires String requiresStr = attribs.getValue(Constants.REQUIRE_CAPABILITY); Parameters requires = new Parameters(requiresStr); for (Entry<String,Attrs> entry : requires.entrySet()) { String ns = Processor.removeDuplicateMarker(entry.getKey()); Attrs attrs = entry.getValue(); CapReqBuilder rb = new CapReqBuilder(ns); for (String key : attrs.keySet()) { if (key.endsWith(":")) rb.addDirective(key.substring(0, key.length() - 1), attrs.get(key)); else rb.addAttribute(key, attrs.getTyped(key)); } RequirementWrapper rw = new RequirementWrapper(); rw.requirement = rb.buildSyntheticRequirement(); addRequirement(requirements, rw); } return requirements; } private static void addCapability(Map<String,List<Capability>> capMap, Capability cap) { List<Capability> capsForNs = capMap.get(cap.getNamespace()); if (capsForNs == null) { capsForNs = new LinkedList<Capability>(); capMap.put(cap.getNamespace(), capsForNs); } capsForNs.add(cap); } private static void addRequirement(Map<String,List<RequirementWrapper>> requirements, RequirementWrapper req) { List<RequirementWrapper> listForNs = requirements.get(req.requirement.getNamespace()); if (listForNs == null) { listForNs = new LinkedList<RequirementWrapper>(); requirements.put(req.requirement.getNamespace(), listForNs); } listForNs.add(req); } private static final String createVersionFilter(String ns, String value, String rangeStr, String versionAttr) { SimpleFilter pkgNameFilter = new SimpleFilter(ns, value); Filter filter = pkgNameFilter; if (rangeStr != null) { VersionRange range = new VersionRange(rangeStr); Filter left; if (range.includeLow()) left = new SimpleFilter(versionAttr, Operator.GreaterThanOrEqual, range.getLow().toString()); else left = new NotFilter(new SimpleFilter(versionAttr, Operator.LessThanOrEqual, range.getLow().toString())); Filter right; if (!range.isRange()) right = null; else if (range.includeHigh()) right = new SimpleFilter(versionAttr, Operator.LessThanOrEqual, range.getHigh().toString()); else right = new NotFilter(new SimpleFilter(versionAttr, Operator.GreaterThanOrEqual, range.getHigh().toString())); AndFilter combined = new AndFilter().addChild(pkgNameFilter).addChild(left); if (right != null) combined.addChild(right); filter = combined; } return filter.toString(); } static List<Clazz> findImportingClasses(String pkgName, Builder builder) throws Exception { List<Clazz> classes = new LinkedList<Clazz>(); Collection<Clazz> importers = builder.getClasses("", "IMPORTING", pkgName); // Remove *this* package for (Clazz clazz : importers) { String fqn = clazz.getFQN(); int dot = fqn.lastIndexOf('.'); if (dot >= 0) { String pkg = fqn.substring(0, dot); if (!pkgName.equals(pkg)) classes.add(clazz); } } return classes; } public File getFile() { return file; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((file == null) ? 0 : file.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; BndBuilderCapReqLoader other = (BndBuilderCapReqLoader) obj; if (file == null) { if (other.file != null) return false; } else if (!file.equals(other.file)) return false; return true; } }