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

aQute.bnd.osgi.AnnotationHeaders Maven / Gradle / Ivy

The newest version!
package aQute.bnd.osgi;

import static aQute.bnd.exceptions.ConsumerWithException.asConsumer;
import static java.util.Collections.emptySet;

import java.io.Closeable;
import java.io.IOException;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.osgi.resource.Namespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import aQute.bnd.annotation.headers.BundleCategory;
import aQute.bnd.annotation.headers.BundleContributors;
import aQute.bnd.annotation.headers.BundleCopyright;
import aQute.bnd.annotation.headers.BundleDevelopers;
import aQute.bnd.annotation.headers.BundleDocURL;
import aQute.bnd.annotation.headers.BundleLicense;
import aQute.bnd.annotation.headers.Category;
import aQute.bnd.bundle.annotations.Capabilities;
import aQute.bnd.bundle.annotations.Capability;
import aQute.bnd.bundle.annotations.Header;
import aQute.bnd.bundle.annotations.Headers;
import aQute.bnd.bundle.annotations.Requirement;
import aQute.bnd.bundle.annotations.Requirements;
import aQute.bnd.exceptions.Exceptions;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Clazz.MethodDef;
import aQute.bnd.osgi.Clazz.QUERY;
import aQute.bnd.osgi.Descriptors.PackageRef;
import aQute.bnd.osgi.Descriptors.TypeRef;
import aQute.bnd.stream.MapStream;
import aQute.bnd.unmodifiable.Sets;
import aQute.bnd.version.Version;
import aQute.bnd.version.VersionRange;
import aQute.lib.collections.MultiMap;
import aQute.lib.converter.Converter;
import aQute.lib.strings.Strings;

/**
 * This class parses the 'header annotations'. Header annotations are
 * annotations that cause headers in the manifest. There are a number of those
 * headers annotations defined in the aQute.bnd.annotation.headers package, e.g.
 * {@link BundleCopyright}. This module applies the semantics of the defined
 * fields in those annotations. It is called at the post parse phase in
 * Analyzer. This {@link ClassDataCollector} is called for all classes in our
 * scope. We first look if any header annotations are applied. We also keep
 * track of what other annotations are applied to these classes. After all the
 * classes have been parsed, we look at any of the annotations that was applied
 * to one of the contained classes. These annotations are also parsed then to
 * check if they have header annotations applied to them.
 * 

* This may sound a bit bizarre, so let me explain. The idea is that you can * create a custom annotation for a specific resource. * *

 * @RequireCapability("osgi.webresource;filter:='(&(osgi.
 * webresource=/google/angular)(version>=${@version}))") @interface
 * Angular {}
 * 
* * Now all a user has to do is apply the @Angular annotation. It will then * automatically create a Require-Capability, with the version of the package. * *
 *  @Angular public class MySpace {...}
 * 
* * {@link About} provides some more information. */ class AnnotationHeaders extends ClassDataCollector implements Closeable { private static final Logger logger = LoggerFactory.getLogger(AnnotationHeaders.class); private static final Converter CONVERTER; static { CONVERTER = new Converter(); CONVERTER.hook(null, (t, o) -> { if (o.getClass() .isArray() && String.class.equals(t)) { return Strings.join(",", (Object[]) o); } return null; }); } private static final Instruction ANNOTATION_INSTRUCTION = new Instruction( "java.lang.annotation.Annotation"); static final Pattern SIMPLE_PARAM_PATTERN = Pattern .compile("\\$\\{(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)\\}"); // Annotations to ignore scanning further because they are known to be // uninteresting // to this scanner. This speeds us up (a little) and also avoids printing // warnings // for annotations that aren't supposed to be needed on the classpath // (usually // OSGi versioning annotations). static final Set DO_NOT_SCAN = Sets.of( "org.osgi.annotation.versioning.ProviderType", "org.osgi.annotation.versioning.ConsumerType", "org.osgi.annotation.versioning.Version"); // // Constant Strings for a fast switch statement // static final String CARDINALITY = "aQute.bnd.annotation.Cardinality"; static final String RESOLUTION = "aQute.bnd.annotation.Resolution"; static final String BUNDLE_LICENSE = "aQute.bnd.annotation.headers.BundleLicense"; static final String REQUIRE_CAPABILITY = "aQute.bnd.annotation.headers.RequireCapability"; static final String PROVIDE_CAPABILITY = "aQute.bnd.annotation.headers.ProvideCapability"; static final String BUNDLE_CATEGORY = "aQute.bnd.annotation.headers.BundleCategory"; static final String BUNDLE_DOC_URL = "aQute.bnd.annotation.headers.BundleDocURL"; static final String BUNDLE_DEVELOPERS = "aQute.bnd.annotation.headers.BundleDevelopers"; static final String BUNDLE_CONTRIBUTORS = "aQute.bnd.annotation.headers.BundleContributors"; static final String BUNDLE_COPYRIGHT = "aQute.bnd.annotation.headers.BundleCopyright"; static final String STD_REQUIREMENT = "org.osgi.annotation.bundle.Requirement"; static final String STD_REQUIREMENT_CARDINALITY = "org.osgi.annotation.bundle.Requirement$Cardinality"; static final String STD_REQUIREMENT_RESOLUTION = "org.osgi.annotation.bundle.Requirement$Resolution"; static final String STD_REQUIREMENTS = "org.osgi.annotation.bundle.Requirements"; static final String STD_CAPABILITY = "org.osgi.annotation.bundle.Capability"; static final String STD_CAPABILITIES = "org.osgi.annotation.bundle.Capabilities"; static final String STD_HEADER = "org.osgi.annotation.bundle.Header"; static final String STD_HEADERS = "org.osgi.annotation.bundle.Headers"; public static Set BND_ANNOTATIONS = Set.of(BUNDLE_LICENSE, REQUIRE_CAPABILITY, PROVIDE_CAPABILITY, BUNDLE_CATEGORY, BUNDLE_DOC_URL, BUNDLE_DEVELOPERS, BUNDLE_CONTRIBUTORS, BUNDLE_COPYRIGHT, STD_REQUIREMENT, STD_CAPABILITY, STD_HEADER); // Used to detect attributes and directives on Require-Capability and // Provide-Capability static final String STD_ATTRIBUTE = "org.osgi.annotation.bundle.Attribute"; static final String STD_DIRECTIVE = "org.osgi.annotation.bundle.Directive"; final Analyzer analyzer; final MultiMap headers = new MultiMap<>(); final Set loggedMissing = new HashSet<>(); final Instructions instructions; Clazz current; boolean finalizing; static String convert(Object value) { try { return CONVERTER.convert(String.class, value); } catch (Exception e) { throw Exceptions.duck(e); } } /* * Initialize */ AnnotationHeaders(Analyzer analyzer) { this(analyzer, new Instructions("*")); } AnnotationHeaders(Analyzer analyzer, Instructions instructions) { this.analyzer = analyzer; this.instructions = instructions; } @Override public boolean classStart(Clazz c) { // // Parse any annotated classes except annotations // current = c; if (!c.isAnnotation() && !c.annotations() .isEmpty()) { for (Instruction instruction : instructions.keySet()) { if (instruction.matches(c.getFQN())) { if (instruction.isNegated()) { return false; } return true; } } } return false; } @Override public void classEnd() throws Exception { current = null; } /* * Called when an annotation is found. Dispatch on the known types. */ @SuppressWarnings("deprecation") @Override public void annotation(Annotation annotation) throws Exception { TypeRef name = annotation.getName(); String fqn = name.getFQN(); if (name.isJava() || DO_NOT_SCAN.contains(fqn)) return; switch (fqn) { case BUNDLE_CATEGORY : doBundleCategory(annotation, annotation.getAnnotation(BundleCategory.class)); break; case BUNDLE_CONTRIBUTORS : doBundleContributors(annotation, annotation.getAnnotation(BundleContributors.class)); break; case BUNDLE_COPYRIGHT : doBundleCopyright(annotation, annotation.getAnnotation(BundleCopyright.class)); break; case BUNDLE_DEVELOPERS : doBundleDevelopers(annotation, annotation.getAnnotation(BundleDevelopers.class)); break; case BUNDLE_DOC_URL : doBundleDocURL(annotation, annotation.getAnnotation(BundleDocURL.class)); break; case BUNDLE_LICENSE : doLicense(annotation, annotation.getAnnotation(BundleLicense.class)); break; case PROVIDE_CAPABILITY : doProvideCapability(annotation, annotation.getAnnotation(aQute.bnd.annotation.headers.ProvideCapability.class)); break; case REQUIRE_CAPABILITY : doRequireCapability(annotation, annotation.getAnnotation(aQute.bnd.annotation.headers.RequireCapability.class)); break; case STD_CAPABILITIES : Capability[] capabilities = annotation.getAnnotation(Capabilities.class) .value(); Object[] capAnnotations = annotation.get("value"); for (int i = 0; i < capabilities.length; i++) { doCapability((Annotation) capAnnotations[i], capabilities[i]); } break; case STD_CAPABILITY : doCapability(annotation, annotation.getAnnotation(Capability.class)); break; case STD_HEADER : Header header = annotation.getAnnotation(Header.class); add(annotation, header.name(), header.value()); break; case STD_HEADERS : Header[] headers = annotation.getAnnotation(Headers.class) .value(); Object[] headerAnnotations = annotation.get("value"); for (int i = 0; i < headers.length; i++) { add((Annotation) headerAnnotations[i], headers[i].name(), headers[i].value()); } break; case STD_REQUIREMENT : doRequirement(annotation, annotation.getAnnotation(Requirement.class)); break; case STD_REQUIREMENTS : Requirement[] requirements = annotation.getAnnotation(Requirements.class) .value(); Object[] reqAnnotations = annotation.get("value"); for (int i = 0; i < requirements.length; i++) { doRequirement((Annotation) reqAnnotations[i], requirements[i]); } break; default : // Handle annotations in a repeatable container annotation Object value = annotation.get("value"); if (value instanceof Object[] container) { if ((container.length > 0) && (container[0] instanceof Annotation contained)) { if (Optional.ofNullable(analyzer.findClass(contained.getName())) .flatMap(c -> c.annotations("java/lang/annotation/Repeatable") .findFirst()) .filter(a -> name.equals(a.get("value"))) .isPresent()) { for (Object a : container) { Annotation repeatable = (Annotation) a; doAnnotatedAnnotation(repeatable, repeatable.getName(), emptySet(), new Attrs()); } } } } doAnnotatedAnnotation(annotation, name, emptySet(), new Attrs()); break; } } /** * Handle the case where an annotation is annotated by one of our header * annotations. * * @param annotation * @param name * @throws Exception */ void doAnnotatedAnnotation(final Annotation annotation, TypeRef name, Set processed, Attrs baseAttrs) throws Exception { final String fqn = name.getFQN(); if (processed.contains(fqn)) { return; } // No point scanning types that definitely aren't going anywhere if (name.isJava() || DO_NOT_SCAN.contains(fqn)) { return; } final Clazz c = analyzer.findClass(name); if (c == null) { // Don't repeatedly log for the same missing annotation if (loggedMissing.add(fqn)) { // Only issue a warning if pedantic if (analyzer.isPedantic()) { analyzer.warning( "Unable to determine whether the meta annotation %s applied to type %s provides bundle annotations as it is not on the project build path. If this annotation does provide bundle annotations then it must be present on the build path in order to be processed", fqn, current.getFQN()); } else { logger.debug( "Unable to determine whether the meta annotation {} applied to type {} provides bundle annotations as it is not on the project build path. If this annotation does provide bundle annotations then it must be present on the build path in order to be processed", fqn, current.getFQN()); } } } else { // If this annotation has meta-annotations then it may be relevant // to us if (!c.annotations() .isEmpty()) { c.parseClassFileWithCollector(new MetaAnnotationCollector(c, annotation, processed, baseAttrs)); } } } private final class MetaAnnotationCollector extends ClassDataCollector { private final Clazz c; private final Annotation annotation; private MethodDef lastMethodSeen; private Set processed; private Attrs attributesAndDirectives = new Attrs(); private MetaAnnotationCollector(Clazz c, Annotation annotation, Set processed, Attrs baseAttrs) { this.c = c; this.annotation = annotation; this.processed = processed; this.attributesAndDirectives = new Attrs(baseAttrs); } @Override public void annotation(Annotation a) throws Exception { String fqn = a.getName() .getFQN(); switch (fqn) { case BUNDLE_CATEGORY : case BUNDLE_CONTRIBUTORS : case BUNDLE_COPYRIGHT : case BUNDLE_DEVELOPERS : case BUNDLE_DOC_URL : case BUNDLE_LICENSE : case PROVIDE_CAPABILITY : case REQUIRE_CAPABILITY : // Bnd annotations support merging of child // properties, // but this is not in the specification as far as I // can tell a.merge(annotation); a.addDefaults(c); AnnotationHeaders.this.annotation(a); break; case STD_CAPABILITIES : case STD_CAPABILITY : case STD_REQUIREMENT : case STD_REQUIREMENTS : mergeAttributesAndDirectives(a); AnnotationHeaders.this.annotation(a); break; case STD_HEADER : case STD_HEADERS : mergeAttributesAndDirectives(a); AnnotationHeaders.this.annotation(a); break; case STD_ATTRIBUTE : case STD_DIRECTIVE : handleAttributeOrDirective(a); break; default : Set processed = new HashSet<>(this.processed); processed.add(c.getFQN()); doAnnotatedAnnotation(a, a.getName(), processed, attributesAndDirectives); break; } } private void mergeAttributesAndDirectives(Annotation a) { String fqn = a.getName() .getFQN(); switch (fqn) { case STD_CAPABILITIES : case STD_REQUIREMENTS : Object[] annotations = a.get("value"); for (Object anno : annotations) { mergeAttributesAndDirectives((Annotation) anno); } break; default : if (c.isAnnotation()) { c.methods() .forEach(asConsumer(method -> { TypeRef returnType = method.getType(); if (returnType.isArray()) { returnType = returnType.getComponentTypeRef(); } Clazz c = analyzer.findClass(returnType); if ((c != null) && c.is(QUERY.IMPLEMENTS, ANNOTATION_INSTRUCTION, analyzer)) { return; } Object object = getOrDefault(method); if (object == null) return; a.put("#" + method.getName(), object); })); } if (!attributesAndDirectives.isEmpty()) { Object[] original = a.get("attribute"); int length = (original != null) ? original.length : 0; Object[] updated = new Object[length + attributesAndDirectives.size()]; if (length > 0) { System.arraycopy(original, 0, updated, 0, length); } for (String key : attributesAndDirectives.keySet()) { updated[length++] = attributesAndDirectives.toString(key); } a.put("attribute", updated); } break; } } private void handleAttributeOrDirective(Annotation a) { Object o = annotation.get(lastMethodSeen.getName()); if (o != null) { String attributeName = a.get("value"); if (attributeName == null) { attributeName = lastMethodSeen.getName(); } if (STD_DIRECTIVE.equals(a.getName() .getFQN())) { attributeName += ":"; } if (!attributesAndDirectives.containsKey(attributeName)) { if (lastMethodSeen.getType() .getFQN() .equals(STD_REQUIREMENT_CARDINALITY) || lastMethodSeen.getType() .getFQN() .equals(STD_REQUIREMENT_RESOLUTION)) { o = o.toString() .toLowerCase(Locale.ROOT); } attributesAndDirectives.putTyped(attributeName, o); } } } @Override public void method(MethodDef defined) { lastMethodSeen = defined; } private Object getOrDefault(MethodDef method) { Object object = annotation.get(method.getName()); if (object == null) { try { object = c.getDefaults() .get(method.getName()); } catch (Exception e) { throw Exceptions.duck(e); } } if (object instanceof Collection col) { object = col.toArray(); } if ((object instanceof Object[] typeRefs) && (typeRefs.length > 0) && typeRefs[0] instanceof TypeRef) { Object[] copy = new Object[typeRefs.length]; for (int i = 0; i < typeRefs.length; i++) { // we need to replace the value with the // target type if (Target.class.getName() .equals(typeRefs[i].toString())) { copy[i] = current.getClassName(); } else { copy[i] = typeRefs[i]; } } object = copy; } else if ((object instanceof TypeRef) && Target.class.getName() .equals(object.toString())) { // we need to replace the value with the // target type object = current.getClassName(); } if (object != null) { String returnFQN = method.getType() .getFQN(); if ((returnFQN.equals(CARDINALITY) || returnFQN.equals(RESOLUTION) || returnFQN.equals(STD_REQUIREMENT_CARDINALITY) || returnFQN.equals(STD_REQUIREMENT_RESOLUTION))) { object = object.toString() .toLowerCase(Locale.ROOT); } } return object; } } /* * Called after the class space has been parsed. We then continue to parse * the used annotations. */ @Override public void close() throws IOException {} /* * Bundle-Developers header */ private void doBundleDevelopers(Annotation a, BundleDevelopers annotation) throws IOException { StringBuilder sb = new StringBuilder(annotation.value()); if (!"".equals(annotation.name())) { sb.append(";name='"); escape(sb, annotation.name()); sb.append("'"); } if (annotation.roles().length > 0) { sb.append(";roles='"); escape(sb, annotation.roles()); sb.append("'"); } if (!"".equals(annotation.organizationUrl())) { sb.append(";organizationUrl='"); escape(sb, annotation.organizationUrl()); sb.append("'"); } if (!"".equals(annotation.organization())) { sb.append(";organization='"); escape(sb, annotation.organization()); sb.append("'"); } if (annotation.timezone() != 0) sb.append(";timezone=") .append(annotation.timezone()); add(a, Constants.BUNDLE_DEVELOPERS, sb.toString()); } /* * Bundle-Contributors header */ private void doBundleContributors(Annotation a, BundleContributors annotation) throws IOException { StringBuilder sb = new StringBuilder(annotation.value()); if (!"".equals(annotation.name())) { sb.append(";name='"); escape(sb, annotation.name()); sb.append("'"); } if (annotation.roles().length > 0) { sb.append(";roles='"); escape(sb, annotation.roles()); sb.append("'"); } if (!"".equals(annotation.organizationUrl())) { sb.append(";organizationUrl='"); escape(sb, annotation.organizationUrl()); sb.append("'"); } if (!"".equals(annotation.organization())) { sb.append(";organization='"); escape(sb, annotation.organization()); sb.append("'"); } if (annotation.timezone() != 0) sb.append(";timezone=") .append(annotation.timezone()); add(a, Constants.BUNDLE_CONTRIBUTORS, sb.toString()); } /* * Bundle-Copyright header */ private void doBundleCopyright(Annotation a, BundleCopyright annotation) throws IOException { add(a, Constants.BUNDLE_COPYRIGHT, annotation.value()); } /* * Bundle-DocURL header */ private void doBundleDocURL(Annotation a, BundleDocURL annotation) throws IOException { add(a, Constants.BUNDLE_DOCURL, annotation.value()); } /* * Bundle-Category header */ private void doBundleCategory(Annotation a, BundleCategory annotation) throws IOException { if (annotation.custom().length > 0) for (String s : annotation.custom()) { add(a, Constants.BUNDLE_CATEGORY, s); } if (annotation.value() != null) for (Category s : annotation.value()) { add(a, Constants.BUNDLE_CATEGORY, s.toString()); } } /* * Provide-Capability header */ @SuppressWarnings("deprecation") private void doProvideCapability(Annotation a, aQute.bnd.annotation.headers.ProvideCapability annotation) throws Exception { Parameters p = new Parameters(); Attrs attrs = getAttributes(a, "ns"); directivesAndVersion(attrs, "uses", "mandatory", "effective"); p.put(annotation.ns(), attrs); String value = attrs.remove("name"); if (value != null) attrs.put(annotation.ns(), value); value = attrs.remove("value"); String s = p.toString(); if (value != null) s += ";" + annotation.value(); add(a, Constants.PROVIDE_CAPABILITY, s); } /* * Require-Capability header */ @SuppressWarnings("deprecation") private void doRequireCapability(Annotation a, aQute.bnd.annotation.headers.RequireCapability annotation) throws Exception { Parameters p = new Parameters(); Attrs attrs = getAttributes(a, "ns"); directivesAndVersion(attrs, "filter", "effective", "resolution"); replaceParameters(attrs); if ("".equals(attrs.get(Constants.FILTER_DIRECTIVE))) attrs.remove(Constants.FILTER_DIRECTIVE); p.put(annotation.ns(), attrs); String s = p.toString(); String extra = annotation.extra(); if (extra != null) { extra = extra.trim(); if (extra.length() > 0) s += ";" + extra; } add(a, Constants.REQUIRE_CAPABILITY, s); } private void replaceParameters(Attrs attrs) throws IllegalArgumentException { for (Entry entry : attrs.entrySet()) { StringBuilder sb = new StringBuilder(); String value = entry.getValue(); Matcher matcher = SIMPLE_PARAM_PATTERN.matcher(value); int start = 0; for (; matcher.find(); start = matcher.end()) { String key = matcher.group(1); String replacement = attrs.get(key); if (replacement == null) { replacement = matcher.group(0); } else if (SIMPLE_PARAM_PATTERN.matcher(replacement) .find()) { throw new IllegalArgumentException("nested substitutions not permitted"); } sb.append(value, start, matcher.start()) .append(replacement); } if (start != 0) { entry.setValue(sb.append(value, start, value.length()) .toString()); } } } /* * Bundle-License header */ private void doLicense(Annotation a, BundleLicense annotation) throws Exception { Parameters p = new Parameters(); p.put(annotation.name(), getAttributes(a, "name")); add(a, Constants.BUNDLE_LICENSE, p.toString()); } /* * Require-Capability header */ private void doRequirement(Annotation a, Requirement annotation) throws Exception { StringBuilder req = new StringBuilder(); req.append(annotation.namespace()); String filter = getFilter(a, annotation); if (!filter.isEmpty()) { try { Verifier.verifyFilter(filter, 0); } catch (Exception e) { analyzer.exception(e, "The Requirement annotation with namespace %s applied to class %s has invalid filter information.", annotation.namespace(), current.getFQN()); } req.append(';') .append(Namespace.REQUIREMENT_FILTER_DIRECTIVE) .append(':') .append('=') .append('\'') .append(filter) .append('\''); } if (a.containsKey("resolution")) { String resolution = annotation.resolution() .toLowerCase(Locale.ROOT); switch (resolution) { case Namespace.RESOLUTION_MANDATORY : case Namespace.RESOLUTION_OPTIONAL : req.append(';') .append(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE) .append(':') .append('=') .append(resolution); break; default : analyzer.error("Requirement annotation in %s has invalid resolution value: %s", current, annotation.resolution()); break; } } if (a.containsKey("cardinality")) { String cardinality = annotation.cardinality() .toLowerCase(Locale.ROOT); switch (cardinality) { case Namespace.CARDINALITY_SINGLE : case Namespace.CARDINALITY_MULTIPLE : req.append(';') .append(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE) .append(':') .append('=') .append(cardinality); break; default : analyzer.error("Requirement annotation in %s has invalid cardinality value: %s", current, annotation.cardinality()); break; } } if (a.containsKey("effective")) { req.append(';') .append(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE) .append(':') .append('='); escape(req, annotation.effective()); } for (String attr : annotation.attribute()) { req.append(';') .append(attr); } add(a, Constants.REQUIRE_CAPABILITY, req.toString()); } private String getFilter(Annotation a, Requirement annotation) { StringBuilder filter = new StringBuilder(); boolean addAnd = false; if (a.containsKey("filter")) { filter.append(annotation.filter()); addAnd = true; } boolean andAdded = false; if (a.containsKey("name")) { filter.append('(') .append(annotation.namespace()) .append('=') .append(annotation.name()) .append(')'); if (addAnd) { filter.insert(0, "(&") .append(')'); andAdded = true; } addAnd = true; } if (a.containsKey("version")) { if (annotation.version() .indexOf('$') == -1) { Version floor; try { floor = Version.parseVersion(annotation.version()); } catch (Exception e) { floor = null; analyzer.exception(e, "The version declared by the Requirement annotation attached to type %s is invalid", current.getFQN()); } if (floor != null) { int current = filter.lastIndexOf(")"); VersionRange range = new VersionRange(floor, floor.bumpMajor()); String rangeFilter = range.toFilter(); filter.append(rangeFilter.substring(2, rangeFilter.length() - 1)); if (andAdded) { filter.deleteCharAt(current) .append(')'); } else if (addAnd) { filter.insert(0, "(&") .append(')'); } } } else { String floor = annotation.version(); int current = filter.lastIndexOf(")"); filter.append("(version>=") .append(floor) .append(")(!(version>=${versionmask;+00;") .append(floor) .append("}))"); if (andAdded) { filter.deleteCharAt(current) .append(')'); } else if (addAnd) { filter.insert(0, "(&") .append(')'); } } } return filter.toString(); } /* * Provide-Capability header */ private void doCapability(Annotation a, Capability annotation) throws Exception { StringBuilder cap = new StringBuilder(); cap.append(annotation.namespace()); if (a.containsKey("name")) { cap.append(';') .append(annotation.namespace()) .append('=') .append(annotation.name()); } if (a.containsKey("version")) { if (annotation.version() .indexOf('$') == -1) { try { Version.parseVersion(annotation.version()); } catch (Exception e) { analyzer.exception(e, "The version declared by the Capability annotation attached to type %s is invalid", current.getFQN()); } } cap.append(";version:Version=") .append(annotation.version()); } for (String attr : annotation.attribute()) { cap.append(';') .append(attr); } if (a.containsKey("uses")) { cap.append(a.stream("uses", TypeRef.class) // .map(TypeRef::getPackageRef) .map(PackageRef::getFQN) .distinct() .collect(Strings.joining(",", ";uses:=\"", "\"", ""))); } if (a.containsKey("effective")) { cap.append(";effective:="); escape(cap, annotation.effective()); } add(a, Constants.PROVIDE_CAPABILITY, cap.toString()); } private void directivesAndVersion(Attrs attrs, String... directives) { for (String directive : directives) { String s = attrs.remove(directive); if (s != null) { attrs.put(directive + ":", s); } } String remove = attrs.remove(Constants.VERSION_ATTRIBUTE); if (remove != null) { attrs.putTyped("version", Version.parseVersion(remove)); } } private Attrs getAttributes(Annotation a, String... ignores) { List ignoresList = Arrays.asList(ignores); Attrs attrs = new Attrs(); MapStream.of(a.entrySet()) .filterKey(key -> !ignoresList.contains(key)) .forEachOrdered(attrs::putTyped); return attrs; } /* * Adds a header. Will preprocess the text. */ private void add(Annotation annotation, String name, String value) throws IOException { if (value == null) return; Processor next = new Processor(); next.addProperties(MapStream.of(annotation.entrySet()) .filterKey(k -> k.startsWith("#")) .map((k, v) -> { if (k.equals("#uses") && (v instanceof Object[] array)) { if ((array.length > 0) && (array[0] instanceof TypeRef)) { String converted = Arrays.stream(array) .map(TypeRef.class::cast) .map(TypeRef::getPackageRef) .map(PackageRef::getFQN) .distinct() .collect(Collectors.joining(",")); return MapStream.entry(k, converted); } } return MapStream.entry(k, convert(v)); }) .collect(MapStream.toMap())); next.setProperty("@class", current.getFQN()); next.setProperty("@class-short", current.getClassName() .getShortName()); PackageRef pref = current.getClassName() .getPackageRef(); next.setProperty("@package", pref.getFQN()); Attrs info = analyzer.getClasspathExports() .get(pref); if (info == null) info = analyzer.getContained() .get(pref); if (info != null && info.containsKey("version")) { next.setProperty("@version", info.get("version")); } Macro macro = next.getReplacer(); /* * These strings come from code, which might also be included from * external parties. So we just do not want to call any system commands * from these sources */ boolean prev = macro.setNosystem(true); try { // Re-parse to strip off any macro output that resolved to empty // strings value = OSGiHeader.parseHeader(macro.process(value)) .toString(); headers.add(name, value); if (!analyzer.keySet() .contains(name)) { // The header isn't in the bnd configuration, so we need to add // it lest bnd completely ignores the header added by the // annotation analyzer.setProperty(name, value); } next.close(); } finally { macro.setNosystem(prev); } } /* * This method is a pass thru for the properties of the analyzer. If we have * such a header, we get the analyzer header and concatenate our values * after removing dups. */ public String getHeader(String name) { String value = analyzer.getProperty(name); if (headers.containsKey(name)) { // // Remove duplicates and sort // Set set = new TreeSet<>(headers.get(name)); List result = new ArrayList<>(set.size() + 1); if (value != null && !set.contains(value)) result.add(value); // analyzer value at start of list result.addAll(set); return Strings.join(result); } return value; } private void escape(StringBuilder app, String s[]) throws IOException { String joined = Strings.join(s); escape(app, joined); } private void escape(StringBuilder app, String s) throws IOException { Processor.quote(app, s); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy