Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parses META-INF/services files for annotations #5912

Merged
merged 2 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1735,6 +1736,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 @@ -1748,6 +1750,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