0

I want to implement storing of enabled or disabled features into database row. When some String value is received from them the network I would like to compare it into ENUM.

ENUM:

public enum TerminalConfigurationFeatureBitString {
    Authorize("authorize", 0), // index 0 in bit string
    Authorize3d("authorize3d", 1), // index 1 in bit String
    Sale("sale", 2), // index 2 in bit String
    Sale3d("sale3d", 3), // index 3 in bit String
}

Map<TerminalConfigurationFeatureBitString, Boolean> featureMaps =
    config.initFromDatabaseValue(optsFromDatabase);

featureMaps.get(transaction.transactionType);

The best way is to use featureMaps.get(TerminalConfigurationFeatureBitString.Sale);

But I don't know the incoming string what would be.

Now I get warning Unlikely argument type String for get(Object) on a Map<TerminalConfigurationFeatureBitString,Boolean>

Is there any other way to make a query into the ENUM without knowing the key?

Michael
  • 41,989
  • 11
  • 82
  • 128
Peter Penzov
  • 1,126
  • 134
  • 430
  • 808
  • Enum constants are just that: constants. They should be uppercase. – Michael Feb 22 '19 at 10:57
  • 2
    use `TerminalConfigurationnFeatureBitString.valueOf(transaction.transactionType)` and then call `featureMaps.get()` with the returned, resolved enum – Lino Feb 22 '19 at 10:58
  • he doesn't need the map in this case – Sharon Ben Asher Feb 22 '19 at 11:00
  • @Lino It's bad practice to rely on the name of an enum, just as it is to rely on the ordinal. It leads to brittle code. You should be able to refactor variable names without fear of changing runtime behaviour. If you rely on methods like that, you can't. – Michael Feb 22 '19 at 11:03

3 Answers3

2

In cases like these, I often find myself adding a static method getByX which does a lookup based upon a property of the enum:

public enum BitString {
    //...

    public static Optional<BitString> getByTransactionType(String transactionType)
    {
        return Arrays.stream(values())
            .filter(x -> x.transactionType.equals(transactionType))
            .findFirst();
    }
}

Usage:

enum TransactionStatus
{
    ENABLED, NOT_ENABLED, NOT_SUPPORTED
}

TransactionStatus status = BitString.getBygetByTransactionType(transaction.transactionType)
    .map(bitString -> featureMaps.get(bitString))
    .map(enabled -> enabled ? TransactionStatus.ENABLED : TransactionStatus.NOT_ENABLED)
    .orElse(TransactionStatus.NOT_SUPPORTED);
Michael
  • 41,989
  • 11
  • 82
  • 128
2

Similar to @Michael's answer, you can just generate a static lookup map inside your enum which maps an enums transaction type to the actual enum:

private static final Map<String, TerminalConfigurationFeatureBitString> TRANSACTION_TYPE_TO_ENUM = 
   Arrays.stream(values()).collect(Collectors.toMap(
       TerminalConfigurationFeatureBitString::getTransactionType, 
       Function.identity()
   );

And then have a lookup method, also inside the enum:

public static TerminalConfigurationFeatureBitString getByTransactionType(String transactionType) {
    TerminalConfigurationFeatureBitString bitString = TRANSACTION_TYPE_TO_ENUM.get(transactionType);
    if(bitString == null) throw new NoSuchElementException(transactionType);
    return bitString;
}

This in a way more performant than the mentioned answer, because the Map is created the first time the enum is loaded (So when it is the first time referenced). And thus the iteration happens only once. Also Maps have a rather fast lookup time so you could say that getting an enum this way works O(1) (when ignoring the initial computation time of O(n))

Lino
  • 19,604
  • 6
  • 47
  • 65
  • I thought it might be nice if Lombok could generate stuff like this automatically. Maybe I'll fork it one day. – Michael Feb 22 '19 at 11:11
  • 1
    @Michael I know at least that Swagger does it, and I use a lot of swagger to generate my models – Lino Feb 22 '19 at 11:12
  • if already using java 8 stream so smartly, why not use `Optional` to avoid the null check in the static method? – Sharon Ben Asher Feb 22 '19 at 11:17
  • @SharonBenAsher this is very opiniated, but I just don't like to use `Optional` for simple conditional logic, of course returning an `Optional` would have the same effect as returning `null` I would just give the caller the responsibilty to do an exceptional action if nothing is found – Lino Feb 22 '19 at 11:20
  • it is my opinion that Optional is a good fit here. there is no "too simple" condition that is below the need for Optional. it was designed to replace null checks with fluent api – Sharon Ben Asher Feb 22 '19 at 11:22
  • i dont mean return an Optional. I meant replace the if statement with Optional – Sharon Ben Asher Feb 22 '19 at 11:25
  • @SharonBenAsher It was **not** designed to replace null checks with a fluent API. It was designed ["to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors"](https://stackoverflow.com/a/26328555/1898563). – Michael Feb 22 '19 at 11:27
  • something like `return Optional.ofNullabe(TRANSACTION_TYPE_TO_ENUM.get(transactionType)).orElseThorw(() -> new NoSuchElementException(transactionType);` – Sharon Ben Asher Feb 22 '19 at 11:29
  • @Michael, well, [Oracle seems to think differently](https://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html) – Sharon Ben Asher Feb 22 '19 at 11:31
  • 1
    @SharonBenAsher I'm literally quoting Brian Goetz, Java's lead language architect. And **your own link disagrees with you** "The purpose of Optional is not to replace every single null reference in your codebase but rather to help design better APIs in which—just by reading the signature of a method—users can tell whether to expect an optional value" – Michael Feb 22 '19 at 11:32
2

You can extend your enum with extra static method which will try to convert given String on enum item:

enum TerminalConfigurationFeatureBitString {
    Authorize("authorize", 0), // index 0 in bit string
    Authorize3d("authorize3d", 1), // index 1 in bit String
    Sale("sale", 2), // index 2 in bit String
    Sale3d("sale3d", 3); // index 3 in bit String

    private final String value;
    private final int index;

    TerminalConfigurationFeatureBitString(String value, int index) {
        this.value = value;
        this.index = index;
    }

    public String getValue() {
        return value;
    }

    public int getIndex() {
        return index;
    }

    public static Optional<TerminalConfigurationFeatureBitString> fromValue(String value) {
        for (TerminalConfigurationFeatureBitString item : values()) {
            if (item.value.equals(value)) {
                return Optional.of(item);
            }
        }

        return Optional.empty();
    }
}

In case option is not found, return Optional.empty(). If feature is not present it means String representation does not represent any feature. Usage:

public void test() {
    EnumMap<TerminalConfigurationFeatureBitString, Boolean> featureMaps = new EnumMap<>(
        TerminalConfigurationFeatureBitString.class);

    Optional<TerminalConfigurationFeatureBitString> feature = TerminalConfigurationFeatureBitString.fromValue("authorize");
    if (!feature.isPresent()) {
        System.out.println("Feature is not foudn!");
    } else {
        Boolean authorize = featureMaps.get(feature.get());
        if (authorize != null && authorize) {
            System.out.println("Feature is enabled!");
        } else {
            System.out.println("Feature is disabled!");
        }
    }
}
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • Ok but when key is not found how I can resume the code? I don't need IllegalArgumentException. – Peter Penzov Feb 22 '19 at 11:34
  • @PeterPenzov, In this case just `return null;` instead of throwing exception. Or choose default value which will be returned in that case. There is many option which depends from scenario you have. – Michał Ziober Feb 22 '19 at 11:39
  • It turns out that I need to add one more case: If the transaction is not found I want to return null and print not found message. If the transaction is not enabled I would like to return message: Not enabled. Any idea how this can be implemented? – Peter Penzov Feb 22 '19 at 14:42
  • @PeterPenzov, Use `Optional` class. In case it returns empty `Optional` it means given `String` is not valid feature. After that check feature in `Map`. If `map.get(...)` returns `null` or `false` it means feature is disabled; otherwise - `enabled`. – Michał Ziober Feb 22 '19 at 15:01