Skip to content

Commit

Permalink
Merge pull request #5912 from pkriens/feature/metainf-services
Browse files Browse the repository at this point in the history
Parses META-INF/services files for annotations
  • Loading branch information
pkriens authored Dec 4, 2023
2 parents 082bdf2 + fd2d4d1 commit 58b8818
Show file tree
Hide file tree
Showing 11 changed files with 559 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package test.annotationheaders;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue;

import org.junit.jupiter.api.Test;

import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Builder;
import aQute.bnd.osgi.Domain;
import aQute.lib.io.IO;

public class ServiceProviderFileTest {
@SuppressWarnings("unchecked")
@Test
public void testServiceProviderOnPackage() throws Exception {
try (Builder b = new Builder();) {
b.addClasspath(IO.getFile("bin_test"));
b.setProperty("-includeresource", """
META-INF/services/com.example.service.Type;\
literal='\
#import aQute.bnd.annotation.spi.ServiceProvider;\n\
#@ServiceProvider(attribute:List<String>="a=1,b=2", effective:=foobar)\n\
java.lang.String'
""");
b.build();
assertTrue(b.check());
Domain manifest = Domain.domain(b.getJar()
.getManifest());
Parameters provideCapability = manifest.getProvideCapability();

assertThat(provideCapability.get("osgi.service")).isNotNull();
assertThat(provideCapability.get("osgi.service")).containsEntry("objectClass", "com.example.service.Type")
.containsEntry("a", "1")
.containsEntry("b", "2")
.containsEntry("effective:", "active");

assertThat(provideCapability.get("osgi.serviceloader")).isNotNull();
assertThat(provideCapability.get("osgi.serviceloader")
.get("osgi.serviceloader")).isEqualTo("com.example.service.Type");
assertThat(provideCapability.get("osgi.serviceloader"))
.containsEntry("osgi.serviceloader", "com.example.service.Type")
.containsEntry("a", "1")
.containsEntry("b", "2")
.containsEntry("register:", "java.lang.String");

Parameters requireCapability = manifest.getRequireCapability();

System.out.println(provideCapability.toString()
.replace(',', '\n'));
System.out.println(requireCapability.toString()
.replace(',', '\n'));
}
}

@Test
public void testBothMetaInfoAndAnnotations() throws Exception {
try (Builder b = new Builder();) {
b.addClasspath(IO.getFile("bin_test"));
b.setPrivatePackage("test.annotationheaders.spi.providerF");
b.setProperty("-includeresource", """
META-INF/services/com.example.service.Type;\
literal='\
#import aQute.bnd.annotation.spi.ServiceProvider;\n\
#@ServiceProvider(attribute:List<String>="a=1,b=2")\n\
java.lang.String'
""");
b.build();
assertTrue(b.check());
Domain manifest = Domain.domain(b.getJar()
.getManifest());
Parameters provideCapability = manifest.getProvideCapability();
Parameters requireCapability = manifest.getRequireCapability();
assertThat(provideCapability.size()).isEqualTo(4);
assertThat(requireCapability.size()).isEqualTo(2);
}
}

@Test
public void testBothMetaInfoAndAnnotationsNoParentheses() throws Exception {
try (Builder b = new Builder();) {
b.addClasspath(IO.getFile("bin_test"));
b.setPrivatePackage("test.annotationheaders.spi.providerF");
b.setProperty("-includeresource", """
META-INF/services/com.example.service.Type;\
literal='\
#import aQute.bnd.annotation.spi.ServiceProvider;\n\
#@ServiceProvider\n\
java.lang.String'
""");
b.build();
assertTrue(b.check());
Domain manifest = Domain.domain(b.getJar()
.getManifest());
Parameters provideCapability = manifest.getProvideCapability();
Parameters requireCapability = manifest.getRequireCapability();
assertThat(provideCapability.size()).isEqualTo(4);
assertThat(requireCapability.size()).isEqualTo(2);
}
}

}
6 changes: 4 additions & 2 deletions biz.aQute.bndlib/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ Import-Package: \
org.osgi.service.repository;version=latest,\
org.osgi.util.function;version=latest,\
org.osgi.util.promise;version=latest,\
aQute.libg, \
aQute.libg,\
biz.aQute.bnd.annotation;version=project,\
biz.aQute.bnd.util;version=latest,\
slf4j.api;version=latest
slf4j.api;version=latest,\
org.osgi.service.serviceloader,\
org.eclipse.jdt.annotation

-testpath: \
${junit},\
Expand Down
28 changes: 28 additions & 0 deletions biz.aQute.bndlib/src/aQute/bnd/header/Attrs.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -462,6 +463,17 @@ public boolean isEqual(Attrs other) {
return true;
}

/**
* Return this attrs as typed valuesx
*/

public Map<String, Object> toTyped() {
Map<String, Object> result = new HashMap<>();
for (String k : keySet()) {
result.put(k, getTyped(k));
}
return result;
}
public Object getTyped(String adname) {
String s = get(adname);
if (s == null)
Expand Down Expand Up @@ -622,4 +634,20 @@ public Attrs select(Predicate<String> predicate) {
});
return attrs;
}

/**
* Add aliases for all directives. In some cases, like annotations,
* directives cannot have their ':' at the end. In that case we create an
* alias without the ':'. We only create an alias for a directive when there
* is no attribute with that value.
*/
public void addDirectiveAliases() {
for (String k : new HashSet<>(keySet())) {
if (k.endsWith(":")) {
String alias = k.substring(0, k.length() - 1);
if (!containsKey(alias))
putTyped(alias, getTyped(k));
}
}
}
}
2 changes: 1 addition & 1 deletion biz.aQute.bndlib/src/aQute/bnd/header/package-info.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@Version("2.6.0")
@Version("2.7.0")
package aQute.bnd.header;

import org.osgi.annotation.versioning.Version;
25 changes: 22 additions & 3 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ private void analyze0() throws Exception {
analyzed = true;
analyzeContent();

Instructions instructions = new Instructions(
OSGiHeader.parseHeader(getProperty(Constants.BUNDLEANNOTATIONS, "*")));
annotationHeaders = new AnnotationHeaders(this, instructions);

doPlugins();

//
Expand All @@ -232,9 +236,7 @@ private void analyze0() throws Exception {
// built ins
//

Instructions instructions = new Instructions(
OSGiHeader.parseHeader(getProperty(Constants.BUNDLEANNOTATIONS, "*")));
cds.add(annotationHeaders = new AnnotationHeaders(this, instructions));
cds.add(annotationHeaders);

for (Clazz c : classspace.values()) {
cds.parse(c);
Expand Down Expand Up @@ -3862,4 +3864,21 @@ public void addDelta(Jar delta) {
}
}

/**
* Useful to reuse the annotation processing. The Annotation
* can be created from other sources than Java code. This
* is mostly useful for the annotations that generate Manifest
* headers.
*/
public void addAnnotation(Annotation ann, TypeRef c) throws Exception {
Clazz clazz = findClass(c);
if (clazz == null) {
error("analyzer processing annotation %s but the associated class %s is not found in the JAR", c);
return;
}
annotationHeaders.classStart(clazz);
annotationHeaders.annotation(ann);
annotationHeaders.classEnd();
}

}
18 changes: 15 additions & 3 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/AnnotationHeaders.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand Down Expand Up @@ -141,6 +142,10 @@ class AnnotationHeaders extends ClassDataCollector implements Closeable {
static final String STD_HEADER = "org.osgi.annotation.bundle.Header";
static final String STD_HEADERS = "org.osgi.annotation.bundle.Headers";

public static Set<String> 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";
Expand Down Expand Up @@ -180,25 +185,28 @@ 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()) {
current = null;
return false;
}

current = c;
return true;
}
}
}
current = null;
return false;
}

@Override
public void classEnd() throws Exception {
current = null;
}

/*
* Called when an annotation is found. Dispatch on the known types.
*/
Expand Down Expand Up @@ -490,6 +498,10 @@ private Object getOrDefault(MethodDef method) {
}
}

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++) {
Expand Down
3 changes: 3 additions & 0 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/Builder.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import aQute.bnd.metatype.MetatypeAnnotations;
import aQute.bnd.osgi.Descriptors.PackageRef;
import aQute.bnd.osgi.Descriptors.TypeRef;
import aQute.bnd.osgi.metainf.MetaInfServiceParser;
import aQute.bnd.plugin.jpms.JPMSAnnotations;
import aQute.bnd.plugin.jpms.JPMSModuleInfoPlugin;
import aQute.bnd.plugin.jpms.JPMSMultiReleasePlugin;
Expand Down Expand Up @@ -1741,6 +1742,7 @@ public Pattern getDoNotCopy() {
static JPMSModuleInfoPlugin moduleInfoPlugin = new JPMSModuleInfoPlugin();
static SPIDescriptorGenerator spiDescriptorGenerator = new SPIDescriptorGenerator();
static JPMSMultiReleasePlugin jpmsReleasePlugin = new JPMSMultiReleasePlugin();
static MetaInfServiceParser metaInfoServiceParser = new MetaInfServiceParser();

@Override
protected void setTypeSpecificPlugins(PluginsContainer pluginsContainer) {
Expand All @@ -1754,6 +1756,7 @@ protected void setTypeSpecificPlugins(PluginsContainer pluginsContainer) {
pluginsContainer.add(moduleInfoPlugin);
pluginsContainer.add(spiDescriptorGenerator);
pluginsContainer.add(jpmsReleasePlugin);
pluginsContainer.add(metaInfoServiceParser);
super.setTypeSpecificPlugins(pluginsContainer);
}

Expand Down
1 change: 1 addition & 0 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/Descriptors.java
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ public static String fqnToPath(String s) {

public TypeRef getTypeRefFromFQN(String fqn) {
return switch (fqn) {
case "void" -> VOID;
case "boolean" -> BOOLEAN;
case "byte" -> BOOLEAN;
case "char" -> CHAR;
Expand Down
Loading

0 comments on commit 58b8818

Please sign in to comment.