Skip to content

Commit a7bf0ea

Browse files
authored
Merge pull request #242 from Geomatys/master
Refactor the handling of JSR-330, JSR-250 and Jakarta annotations in `ServiceProvider`
2 parents 81d086f + 6a91048 commit a7bf0ea

4 files changed

Lines changed: 214 additions & 168 deletions

File tree

pom.xml

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,11 @@
7171

7272
<!-- Plugins -->
7373
<github.maven.version>0.12</github.maven.version>
74-
<github.global.server>github</github.global.server>
74+
<github.global.server>github</github.global.server>
7575
<license-maven.version>4.1</license-maven.version>
7676
<maven.surefire-report.version>2.22.2</maven.surefire-report.version>
7777
<junit.jupiter.version>5.7.2</junit.jupiter.version>
78+
<jakarta.inject.version>2.0.1</jakarta.inject.version>
7879
<spotbugs-maven.version>4.0.4</spotbugs-maven.version>
7980
<!--Exclude the files here -->
8081
<sonar.exclusions>src/main/java/javax/measure/BinaryPrefix.java,src/main/java/javax/measure/MetricPrefix.java</sonar.exclusions>
@@ -377,7 +378,7 @@
377378
<version>0.5.2</version>
378379
</plugin>
379380

380-
<!--This plugin's configuration is used to store Eclipse m2e settings
381+
<!--This plugin's configuration is used to store Eclipse m2e settings
381382
only. It has no influence on the Maven build itself. -->
382383
<plugin>
383384
<groupId>org.eclipse.m2e</groupId>
@@ -476,7 +477,7 @@
476477
<!-- Coverage -->
477478
<plugin>
478479
<groupId>org.jacoco</groupId>
479-
<artifactId>jacoco-maven-plugin</artifactId>
480+
<artifactId>jacoco-maven-plugin</artifactId>
480481
<configuration>
481482
<excludes>
482483
<exclude>META-INF/versions/**</exclude>
@@ -564,14 +565,14 @@
564565
<configuration>
565566
<!-- Workaround for https://github.com/unitsofmeasurement/unit-api/issues/220 -->
566567
<source>8</source>
567-
</configuration>
568-
<!--
568+
</configuration>
569+
<!--
569570
<configuration>
570571
<sourcepath>src/main/java;src/main/jdk9</sourcepath>
571572
<detectLinks>true</detectLinks>
572573
<keywords>true</keywords>
573574
<linksource>true</linksource>
574-
<failOnError>false</failOnError>
575+
<failOnError>false</failOnError>
575576
<verbose>true</verbose>
576577
<tags>
577578
<tag>
@@ -613,7 +614,7 @@
613614
<manifestEntries>
614615
<Specification-Title>${project.name}</Specification-Title>
615616
<Specification-Version>${project.version}</Specification-Version>
616-
<Specification-Vendor>${project.organization.name}</Specification-Vendor>
617+
<Specification-Vendor>${project.organization.name}</Specification-Vendor>
617618
<Automatic-Module-Name>java.measure</Automatic-Module-Name>
618619
<Multi-Release>true</Multi-Release>
619620
</manifestEntries>
@@ -688,12 +689,12 @@
688689
</excludes>
689690
<headerDefinitions>
690691
<headerDefinition>src/main/config/headers.xml</headerDefinition>
691-
</headerDefinitions>
692+
</headerDefinitions>
692693
</licenseSet>
693694
</licenseSets>
694695
<mapping>
695696
<java>JAVA_STYLE</java>
696-
</mapping>
697+
</mapping>
697698
</configuration>
698699
</plugin>
699700

@@ -793,11 +794,11 @@
793794
</reporting>
794795

795796
<!-- Deployment to public servers -->
796-
<distributionManagement>
797+
<distributionManagement>
797798
<repository>
798799
<id>ossrh</id>
799800
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
800-
</repository>
801+
</repository>
801802
<snapshotRepository>
802803
<id>ossrh</id>
803804
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
@@ -823,6 +824,12 @@
823824
<version>${junit.jupiter.version}</version>
824825
<scope>test</scope>
825826
</dependency>
827+
<dependency>
828+
<groupId>jakarta.inject</groupId>
829+
<artifactId>jakarta.inject-api</artifactId>
830+
<version>${jakarta.inject.version}</version>
831+
<scope>test</scope>
832+
</dependency>
826833
</dependencies>
827834

828835
<!-- Additional repositories -->
@@ -917,7 +924,7 @@
917924
<!-- This profile builds (optional) format elements into separate JAR files -->
918925
<build>
919926
<plugins>
920-
<!--
927+
<!--
921928
<plugin>
922929
<groupId>org.apache.maven.plugins</groupId>
923930
<artifactId>maven-antrun-plugin</artifactId>
@@ -945,7 +952,7 @@
945952
</executions>
946953
</plugin>
947954
-->
948-
955+
949956
<plugin>
950957
<artifactId>maven-jar-plugin</artifactId>
951958
<version>${maven.jar.version}</version>
@@ -1145,7 +1152,7 @@
11451152
<jdkOptionalVersion>9</jdkOptionalVersion>
11461153
<project.build.javaVersion>${jdkVersion}</project.build.javaVersion>
11471154
<maven.compile.targetLevel>${jdkVersion}</maven.compile.targetLevel>
1148-
<maven.compile.sourceLevel>${jdkVersion}</maven.compile.sourceLevel>
1155+
<maven.compile.sourceLevel>${jdkVersion}</maven.compile.sourceLevel>
11491156
</properties>
11501157
</profile>
11511158
</profiles>

src/main/java/javax/measure/spi/ServiceProvider.java

Lines changed: 85 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -66,26 +66,26 @@ public abstract class ServiceProvider {
6666
* Class name of JSR-330 annotation for naming a service provider.
6767
* We use reflection for keeping JSR-330 an optional dependency.
6868
*/
69-
private static final String NAMED_ANNOTATION = "javax.inject.Named";
69+
private static final String LEGACY_NAMED_ANNOTATION = "javax.inject.Named";
7070

7171
/**
7272
* Class name of JSR-250 annotation for assigning a priority level to a service provider.
7373
* We use reflection for keeping JSR-250 an optional dependency.
7474
*/
75-
private static final String PRIORITY_ANNOTATION = "javax.annotation.Priority";
75+
private static final String LEGACY_PRIORITY_ANNOTATION = "javax.annotation.Priority";
7676

7777
/**
7878
* Class name of Jakarta Dependency Injection annotation for naming a service provider.
7979
* We use reflection for keeping Jakata Injection an optional dependency.
8080
*/
81-
private static final String JAKARTA_NAMED_ANNOTATION = "jakarta.inject.Named";
81+
private static final String NAMED_ANNOTATION = "jakarta.inject.Named";
8282

8383
/**
8484
* Class name of Jakarta Common Annotation for assigning a priority level to a service provider.
8585
* We use reflection for keeping Jakarta Annotations an optional dependency.
8686
*/
87-
private static final String JAKARTA_PRIORITY_ANNOTATION = "jakarta.annotation.Priority";
88-
87+
private static final String PRIORITY_ANNOTATION = "jakarta.annotation.Priority";
88+
8989
/**
9090
* The current service provider, or {@code null} if not yet determined.
9191
*
@@ -106,7 +106,8 @@ protected ServiceProvider() {
106106
* Allows to define a priority for a registered {@code ServiceProvider} instance.
107107
* When multiple providers are registered in the system, the provider with the highest priority value is taken.
108108
*
109-
* <p>If the {@value #PRIORITY_ANNOTATION} annotation (from JSR-250) or {@value #JAKARTA_PRIORITY_ANNOTATION} annotation (from Jakarta Annotations) is present on the {@code ServiceProvider}
109+
* <p>If the {@value #PRIORITY_ANNOTATION} annotation (from Jakarta Annotations)
110+
* or {@value #LEGACY_PRIORITY_ANNOTATION} annotation (from JSR-250) is present on the {@code ServiceProvider}
110111
* implementation class, then that annotation (first if both were present) is taken and this {@code getPriority()} method is ignored.
111112
* Otherwise – if a {@code Priority} annotation is absent – this method is used as a fallback.</p>
112113
*
@@ -155,22 +156,28 @@ private static final class Selector implements Predicate<ServiceProvider>, Compa
155156
private final String toSearch;
156157

157158
/**
158-
* Class of the {@value #NAMED_ANNOTATION} and {@value #PRIORITY_ANNOTATION} annotations to search,
159-
* or {@code null} if those classes are not on the classpath.
159+
* The {@code value()} method in the {@value #NAMED_ANNOTATION} annotation,
160+
* or {@code null} if that class is not on the classpath.
160161
*/
161-
private Class<? extends Annotation> namedAnnotation, priorityAnnotation;
162-
162+
private final Method nameGetter;
163+
164+
/**
165+
* The {@code value()} method in the {@value #PRIORITY_ANNOTATION} annotation,
166+
* or {@code null} if that class is not on the classpath.
167+
*/
168+
private final Method priorityGetter;
169+
163170
/**
164-
* Class of the {@value #JAKARTA_NAMED_ANNOTATION} and {@value #JAKARTA_PRIORITY_ANNOTATION} annotations to search,
165-
* or {@code null} if those classes are not on the classpath.
171+
* The {@code value()} method in the {@value #LEGACY_NAMED_ANNOTATION} annotation,
172+
* or {@code null} if that class is not on the classpath.
166173
*/
167-
private Class<? extends Annotation> jakartaNamedAnnotation, jakartaPriorityAnnotation;
174+
private final Method legacyNameGetter;
168175

169176
/**
170-
* The {@code value()} method in the {@code *Annotation} class,
171-
* or {@code null} if those classes are not on the classpath.
177+
* The {@code value()} method in the {@value #LEGACY_PRIORITY_ANNOTATION} annotation,
178+
* or {@code null} if that class is not on the classpath.
172179
*/
173-
private Method nameGetter, priorityGetter;
180+
private final Method legacyPriorityGetter;
174181

175182
/**
176183
* Creates a new filter and comparator for a stream of service providers.
@@ -180,60 +187,73 @@ private static final class Selector implements Predicate<ServiceProvider>, Compa
180187
Selector(String name) {
181188
toSearch = name;
182189
try {
183-
if (name != null) try {
184-
namedAnnotation = Class.forName(NAMED_ANNOTATION).asSubclass(Annotation.class);
185-
nameGetter = namedAnnotation.getMethod("value", (Class[]) null);
186-
} catch (ClassNotFoundException e) {
187-
// Ignore since JSR-330 is an optional dependency.
188-
}
189-
if (nameGetter == null) try { // if nameGetter has not been set already try Jakarta Injection
190-
jakartaNamedAnnotation = Class.forName(JAKARTA_NAMED_ANNOTATION).asSubclass(Annotation.class);
191-
nameGetter = jakartaNamedAnnotation.getMethod("value", (Class[]) null);
192-
} catch (ClassNotFoundException e) {
193-
// Ignore since Jakarta Injection is an optional dependency.
194-
}
195-
196-
try {
197-
priorityAnnotation = Class.forName(PRIORITY_ANNOTATION).asSubclass(Annotation.class);
198-
priorityGetter = priorityAnnotation.getMethod("value", (Class[]) null);
199-
} catch (ClassNotFoundException e) {
200-
// Ignore since JSR-250 is an optional dependency.
201-
}
202-
if (priorityGetter == null) try { // if priorityGetter has not been set already try Jakarta Annotations
203-
jakartaPriorityAnnotation = Class.forName(JAKARTA_PRIORITY_ANNOTATION).asSubclass(Annotation.class);
204-
priorityGetter = jakartaPriorityAnnotation.getMethod("value", (Class[]) null);
205-
} catch (ClassNotFoundException e) {
206-
// Ignore since Jakarta Annotations is an optional dependency.
190+
if (name != null) {
191+
nameGetter = getValueMethod(NAMED_ANNOTATION);
192+
legacyNameGetter = getValueMethod(LEGACY_NAMED_ANNOTATION);
193+
} else {
194+
nameGetter = null;
195+
legacyNameGetter = null;
207196
}
197+
priorityGetter = getValueMethod(PRIORITY_ANNOTATION);
198+
legacyPriorityGetter = getValueMethod(LEGACY_PRIORITY_ANNOTATION);
208199
} catch (NoSuchMethodException e) {
209200
// Should never happen since value() is a standard public method of those annotations.
210201
throw new ServiceConfigurationError("Cannot get annotation value", e);
211202
}
212203
}
213204

214205
/**
215-
* Returns {@code true} if the given service provider has the name we are looking for.
216-
* This method shall be invoked only if a non-null name has been specified to the constructor.
217-
* This method looks for the {@value #NAMED_ANNOTATION} annotation or {@value #JAKARTA_NAMED_ANNOTATION} annotation, and if none are found fallbacks on
218-
* {@link ServiceProvider#toString()}.
206+
* Returns the {@code value()} method in the given annotation class.
207+
*
208+
* @param classname name of the class from which to get the {@code value()} method.
209+
* @return the {@code value()} method, or {@code null} if the annotation class was not found.
219210
*/
220-
@Override
221-
public boolean test(ServiceProvider provider) {
222-
Object value = null;
223-
if (nameGetter != null) {
224-
Annotation a = null;
225-
if (namedAnnotation != null) {
226-
a = provider.getClass().getAnnotation(namedAnnotation);
227-
} else if (jakartaNamedAnnotation != null) {
228-
a = provider.getClass().getAnnotation(jakartaNamedAnnotation);
229-
}
211+
private static Method getValueMethod(final String classname) throws NoSuchMethodException {
212+
try {
213+
return Class.forName(classname).getMethod("value", (Class[]) null);
214+
} catch (ClassNotFoundException e) {
215+
// Ignore because JSR-330, JSR-250 and Jakarta are optional dependencies.
216+
return null;
217+
}
218+
}
219+
220+
/**
221+
* Invokes the {@code value()} method on the annotation of the given class.
222+
* The annotation on which to invoke the method is given by {@link Method#getDeclaringClass()}.
223+
*
224+
* @param provider class of the provider on which to invoke annotation {@code value()}.
225+
* @param getter the preferred {@code value()} method to invoke, or {@code null}.
226+
* @param fallback an alternative {@code value()} method to invoke, or {@code null}.
227+
* @return the value, or {@code null} if none.
228+
*/
229+
private static Object getValue(final Class<?> provider, Method getter, Method fallback) {
230+
if (getter == null) {
231+
getter = fallback;
232+
fallback = null;
233+
}
234+
while (getter != null) {
235+
final Annotation a = provider.getAnnotation(getter.getDeclaringClass().asSubclass(Annotation.class));
230236
if (a != null) try {
231-
value = nameGetter.invoke(a, (Object[]) null);
237+
return getter.invoke(a, (Object[]) null);
232238
} catch (IllegalAccessException | InvocationTargetException e) {
233239
// Should never happen since value() is a public method and should not throw exception.
234240
throw new ServiceConfigurationError("Cannot get annotation value", e);
235241
}
242+
getter = fallback;
243+
fallback = null;
236244
}
245+
return null;
246+
}
247+
248+
/**
249+
* Returns {@code true} if the given service provider has the name we are looking for.
250+
* This method shall be invoked only if a non-null name has been specified to the constructor.
251+
* This method looks for the {@value #NAMED_ANNOTATION} and {@value #LEGACY_NAMED_ANNOTATION}
252+
* annotations in that order, and if none are found fallbacks on {@link ServiceProvider#toString()}.
253+
*/
254+
@Override
255+
public boolean test(final ServiceProvider provider) {
256+
Object value = getValue(provider.getClass(), nameGetter, legacyNameGetter);
237257
if (value == null) {
238258
value = provider.toString();
239259
}
@@ -242,23 +262,13 @@ public boolean test(ServiceProvider provider) {
242262

243263
/**
244264
* Returns the priority of the given service provider.
245-
* This method looks for the {@value #PRIORITY_ANNOTATION} annotation or {@value #JAKARTA_PRIORITY_ANNOTATION},
246-
* and if none are found falls back on {@link ServiceProvider#getPriority()}.
265+
* This method looks for the {@value #PRIORITY_ANNOTATION} and {@value #LEGACY_PRIORITY_ANNOTATION}
266+
* annotations in that order, and if none are found falls back on {@link ServiceProvider#getPriority()}.
247267
*/
248-
private int priority(ServiceProvider provider) {
249-
if (priorityGetter != null) {
250-
Annotation a = null;
251-
if (priorityAnnotation != null) {
252-
a = provider.getClass().getAnnotation(priorityAnnotation);
253-
} else if (jakartaPriorityAnnotation != null) {
254-
a = provider.getClass().getAnnotation(jakartaPriorityAnnotation);
255-
}
256-
if (a != null) try {
257-
return (Integer) priorityGetter.invoke(a, (Object[]) null);
258-
} catch (IllegalAccessException | InvocationTargetException e) {
259-
// Should never happen since value() is a public method and should not throw exception.
260-
throw new ServiceConfigurationError("Cannot get annotation value", e);
261-
}
268+
private int priority(final ServiceProvider provider) {
269+
Object value = getValue(provider.getClass(), priorityGetter, legacyPriorityGetter);
270+
if (value != null) {
271+
return (Integer) value;
262272
}
263273
return provider.getPriority();
264274
}
@@ -328,8 +338,10 @@ public static final List<ServiceProvider> available() {
328338
/**
329339
* Returns the {@link ServiceProvider} with the specified name.
330340
* The given name must match the name of at least one service provider available in the current thread's
331-
* context class loader. The service provider names are the values of {@value #NAMED_ANNOTATION} annotations
332-
* when present, or the value of {@link #toString()} method for providers without {@code Named} annotation.
341+
* context class loader.
342+
* The service provider names are the values of {@value #NAMED_ANNOTATION} (from Jakarta Annotations) or
343+
* {@value #LEGACY_NAMED_ANNOTATION} (from JSR-330) annotations when present (first if both were present),
344+
* or the value of {@link #toString()} method for providers without {@code Named} annotation.
333345
*
334346
* <p>Implementors are encouraged to provide an {@code Named} annotation or to override {@link #toString()}
335347
* and use a unique enough name, e.g. the class name or other distinct attributes.

0 commit comments

Comments
 (0)