user2185573
user2185573

Reputation:

What is a good extensible way to check if a string is a value of more than one Enum Type

Lets say I have a bunch of logically distinct java enums that cannot be merged together since certain prior operations depend on each independent enum.

enum Enum1 {
   ENUM1VAL1, 
   ENUM1VAL2
}

enum Enum2 {
   ENUM2VAL1, 
   ENUM2VAL2
}

void existingOperationOnEnum1(Enum1 enum1);
void existingOperationOnEnum2(Enum2 enum2);

Now lets say I wish to define a new operation that checks whether a string can be converted to any of these enums

void isValidStringConversionForEnums(final String input) {
   ENUM1.valueOf(input); // Throws IllegalArgumentException if conversion not possible
   ENUM2.valueOf(input); // Throws IllegalArgumentException if conversion not possible
}

So this is great, but its not extensible. The business logic is such that we can have new enums be created in the future (with its own set of enum specific operations), however isValidStringConversionForEnums needs to be updated every time a new one is added, which a programmer might easily forget.

So my question is how would I modify isValidStringConversionForEnums so that it works with future extensions.

One way I thought was to create an interface EnumBase, get the Enums to extend that and then use reflection to get all Enum impls, but I do not wish to use Reflection in Runtime unless I have to.

Is there an alternate way in which this can be written such that I add a new enum and somehow isValidStringConversionForEnums handles that new enum.

Upvotes: 1

Views: 68

Answers (1)

rzwitserloot
rzwitserloot

Reputation: 102872

You have 2 independent problems here:

  1. Given a list of enum types, how do you write code that tests that your string is convertible to at least one of them?
  2. How do you obtain that list.

These 2 problems are completely unrelated. So, you solve them separately.

Given a list...

Class<? extends Enum> is probably your best bet.

enum Test1 {
  FOO, BAR;
}
enum Test2 {
  FOO, BAZ;
}

public static void isValidStringConversionForEnums(String in, Class<? extends Enum>... enumTypes) {

  for (var enumType : enumTypes) {
     for (var c : enumType.getEnumConstants()) {
       if (c.name().equals(in)) return true;
     }
  }

  return false;
}

This isn't particularly efficient - but you get the gist. You can pre-make a HashSet containing every valid value and then it's just a .containsKey() call, using the above code to instead serve as a one-off "init" that makes that HashSet for example. If you actually need the enum constants, make a Map<String, List<? extends Enum<?>> instead. computeIfAbsent is a nice way to create those lists on the fly in a one-liner.

Discovery

The java classloader abstraction system simply doesn't have 'give me a list of all files in the jar' or 'give me a list of all classes'. Some libraries appear to provide those features but they are hacks that do not always work, so you probably don't want to use those.

The standard solution for this problem, used by the JVM itself too, is 'SPI' - with module-info.java it's baked in, if you don't use that (and you probably shouldn't, it's kinda tricky, that), there's ServiceLoader. To make the service files, you can use annotation processors, I think there are a few around that automate it.

Then the process flow is simply, to make a new enum, stick a @ProviderFor(Something.class) on it, where Something.class is some marker interface of your design.

Note that wanting to scan all enums on the entire classpath is just plain bad. There are all sorts of enums that java uses internally, what if one of those so happens to have a value in there equal to one of your strings? That does NOT all of a sudden make that a valid string. No, only enums you explicitly mark as applying to whatever this question is, qualify, this, you need to do something to mark them. Either with an annotation, or explicitly by writing out the full list.

Upvotes: 5

Related Questions