Enum-aware ServiceLoader implementation?
Asked Answered
F

1

2

I would like to be able to indicate an enum type as an interface implementation and then load all enums as separate instances/implementations of the interface via the ServiceLoader API. An example of this use case would be to allow downstream users of my API to specify custom values, but provide an enum with standard/common implementations. My interface only requires a String name(), so any enum implements it already.

For example, the CopyOption interface in the Java NIO APIs, with the provided StandardCopyOption enum. Say I wanted to load all CopyOptions, even new ones on the classpath along with the standards, to a single iterator via ServiceLoader (or I'm open to other suggestions!)

I finally got it to work by wholesale copying ServiceLoader and modifying it to try using getEnumConstants when instantiation fails (the part in the try is how it works currently and the part in the catch is what I added/changed):

try {
    S p = service.cast(c.newInstance());
    providers.put(cn, p);
    return p;
} catch (Throwable x) {
    Object[] arr = c.getEnumConstants();
    if (arr == null || arr.length == 0) {
        fail(service, "Provider " + cn + " could not be instantiated", x);  
    }

    List<S> list = new LinkedList<>();
    for (Object o : arr) {
        Enum<?> e = (Enum<?>) o;
        S p = service.cast(e);
        providers.put(cn + e.ordinal(), p);
        list.add(p);
    }
    subiter = list.iterator();
    return subiter.next();
}

I also added some code such that if subiter exists and has next, it is iterated before moving on to the next class name.

My question is: Is there a better way?

In case the end use is not clear, this is now what's possible with the above modifications:

interface ImageType {
    String name();
}

@AutoService(ImageType.class)
enum StandardImageType implements ImageType {
    IMAGE,
    VECTOR,
    RASTER,
    HANDWRITING,
    ICON,
    LOGO,
    SEAL,
    RULE,
    BARCODE
}
Fredericton answered 16/2, 2019 at 4:0 Comment(0)
N
3

With the introduction of Java modules, an alternative to instantiating via default constructor has been added to service providers. But it only work when the provider is in a named module.

The provider class may declare a public static T provider() method where T is the service type. Then, the provider implementation class doesn’t even need to implement or extend T itself.

Since neither, arrays nor a generic type like List<ImageType> can be used as service type, we would need another type for potentially encapsulating multiple actual instances, e.g.

package somemodule;

import java.util.function.Supplier;

public interface ImageType {
    String name();
    interface ImageTypes extends Supplier<ImageType[]> {}
}

and

package somemodule;

public enum StandardImageType implements ImageType {
    IMAGE,
    VECTOR,
    RASTER,
    HANDWRITING,
    ICON,
    LOGO,
    SEAL,
    RULE,
    BARCODE;

    public static ImageTypes provider() {
        return StandardImageType::values;
    }
}

and a module declaration like

module SomeModule {
    uses somemodule.ImageType.ImageTypes;
    provides somemodule.ImageType.ImageTypes with somemodule.StandardImageType;
}

which allows to write, e.g.

List<ImageType> all = ServiceLoader.load(ImageType.ImageTypes.class)
        .stream().flatMap(p -> Arrays.stream(p.get().get()))
        .collect(Collectors.toList());

somewhere within the module (or any other module with a uses somemodule.ImageType.ImageTypes; declaration).

Newsreel answered 6/1, 2020 at 18:59 Comment(7)
Wow! great news- thank you! and to confirm: this will still pick up/iterate through "normal" implementations e.g. @AutoService(ImageType.class) class MyCustomImageType implements ImageType { ... in addition to the ones provided via an explicit provider?Fredericton
This depends on the framework which implements the @AutoService feature. As said, the new feature only works with declared modules, so the framework must generate provides declarations (or inject their compiled equivalent) to the module-info. Writing the class name to a file in META-INF/services won't work with modules.Newsreel
That last sentence answered my question thank you. I was referring to Google's @AutoService annotation as a convenience for maintaining META-INF/services but that's what my question was getting atFredericton
Shouldn't StandardImageType also implement ImageTypes interface, or is that no longer a requirement? Intellij seems to complain about it even when using provider static method.Cliftonclim
@Cliftonclim it works in Netbeans and the JLS says explicitly: “Also, note that when a service provider declares a provider method, the service provider itself need not be a subtype of the service.Newsreel
Right. It was actually my error, since provider method was only added at java 9, while im still on 8.Cliftonclim
@Cliftonclim I hinted at it in my answer when I wrote "But it only work when the provider is in a named module"...Newsreel

© 2022 - 2024 — McMap. All rights reserved.