0

I am using Java 17.

I have created a library with a bunch of utility stuff, including a "Service", doing something. This Service is published both the old style (using a file META-INF/services/<name_of_the_Service_interface>) and also through the module-info.java file.

It works in JUnit tests as well as in the context of a bunch of programs I wrote so far, no matter if the programs are modularised or not.

But when used within the context of an AnnotationProcessor, it does not work. The AnnotationProcessor is invoked by javac.

This is the code that works for all other contexts so far:

final var moduleLayer = StringConverter.class.getModule().getLayer();
final var converters = isNull( moduleLayer ) ? ServiceLoader.load( StringConverter.class ) : ServiceLoader.load( moduleLayer, StringConverter.class );

ServiceLoop:for( final StringConverter<?> c : converters )
{
  for( final var subjectClass : retrieveSubjectClasses( c ) )
  {
     buffer.put( subjectClass, converter.getClass() ) );
  }
}   //  ServiceLoop:

When called from inside an AnnotationProcessor, the ServiceLoop is never executed because the ServiceLoader did not find any implementation.

Originally, the code lived in the library jar, but even when I copied it into the AnnotationProcessor, it does not work.

My workaround so far is to read the META-INF/services/StringConverter file myself ('StringConverter' is a placeholder here, originally it has a proper class name), and to rebuild the functionality of java.util.ServiceLoader:

final var classLoader = AP.class.getClassLoader();
final var resources = classLoader.getResources( format( "META-INF/services/%s", StringConverter.class.getName() ) );
for( final var file : list( resources ) )
{
  try( final var reader = new BufferedReader( new InputStreamReader( file.openStream() ) ) )
  {
    final var converterClasses = reader.lines()
      .map( String::trim )
      .filter( l -> !l.startsWith( "#" ) )
      .map( l -> loadClass( classLoader, l, StringConverter.class ) )
      .filter( Optional::isPresent )
      .map( Optional::get )
      .toList();
    CreateLoop: for( final var c : converterClasses )
    {
      try
      {
        final var constructor = c.getConstructor();
        final var instance = constructor.newInstance();
        for( final var subjectClass : retrieveSubjectClasses( instance ) )
        {
           buffer.put( subjectClass, c );
        }
      }
      catch( final InvocationTargetException | NoSuchMethodException |InstantiationException | IllegalAccessException e )
      {
        /* Deliberately ignored! */
        continue CreateLoop;
      }
    }   //  CreateLoop:
  }
}

Basically, this works.

But of course, I would like to avoid that workaround!

Any idea why java.util.ServiceLoader refuse to work when invoked in the context of an AnnotationProcessor called by javac?

tquadrat
  • 3,033
  • 1
  • 16
  • 29
  • note that the annotation processor needa to be compiled before the rest of the project is compiled. – dan1st Jan 26 '22 at 11:01
  • The Annotation Processor is provided as a separate Jar file – it is precompiled all the time, as part of a completely different project (the project to build the Annotation Processor). – tquadrat Jan 26 '22 at 13:13
  • 1
    Is the service loader part of the annotation processor jar (or otherwise on that classpath), or is it part of the project being compiled (and having code generated by the annotation processor)? If it is the latter, the service instance hasn't been compiled yet, so can't be loaded - that's what @dan1st is saying. – Colin Alworth Jan 29 '22 at 23:24
  • @ColinAlworth – The ServiceLoader is part of the AnnotationProcessor, and not part of the code that is processed by the AnnotationProcessor. And the AnnotationProcessor is an independent project. – tquadrat Jan 30 '22 at 11:03
  • Understood (and this is clear since you reference StringConverter.class in your processor) - I'm asking about the service instance that isn't loading correctly. If that service file were in the to-be-compiled project, that could cause this. I would also want to understand exactly how you're calling javac, to ensure that you aren't running into an issue where it is on the classpath vs the annotation classpath or vice versa. – Colin Alworth Jan 30 '22 at 21:32
  • The AP Jar is on the CLASSPATH for `javac`, and in addition, it is mentioned in the `-processorpath` option. Finally, the AP itself is listed with the `-processor` option. And my code to mimic `ServiceLoader` is a simplified implementation of that class itself. Finally, the service files are not part of the code to compile. Btw, what is the annotation CLASSPATH and how is it different from the CLASSPATH? – tquadrat Jan 31 '22 at 07:57

0 Answers0