0

How do write code to loop round an arbitary enum ?

So in the code below in use an enum as the values to provide to create a Html select loop, but I cannot see how to pass an enum to the method, or how to access the standard .values() and .ordinal() methods. so currently I have hardcoded a particular enum RenameFileOptions but I want to work with any enum, I could parse enum class e.g RenameFileOptions.class but still how would I access .name(), .values() and .ordinal()

public ContainerTag addCombo(UserOption userOption, int selectedValue)
{
    return div(
                label(userOption.getLabel().getMsg())
                    .withTitle(userOption.getTooltip().getMsg()),
                select(
                    each(Arrays.asList(RenameFileOptions.values()),
                            next ->
                                iffElse(next.ordinal()==selectedValue,
                                        option(next.name()).attr(Html.SELECTED, Html.SELECTED).withValue(String.valueOf(next.ordinal())),
                                        option(next.name()).withValue(String.valueOf(next.ordinal()))
                                )
                    ))
                .withName(userOption.getOption())
            );
}

Update As there seemed to be no way to achieve this in a none hacky way I instead added a getOptions() methods to each enum, and this is what is sent to my addCombo method. It means I have to essentially repeat code, which I dont like doing but it means the addCombo() method works as required and keeps the code easier to understand by not introducing difficult syntax.

public enum RenameFileOptions implements OptionList
{
    NO(TextLabel.RENAME_FILE_OPTION_NO),
    YES_IF_MATCHED_TO_RELEASE(TextLabel.RENAME_FILE_OPTION_YES_IF_RELEASE_MATCH),
    YES_IF_MATCHED_TO_RELEASE_OR_SONG(TextLabel.RENAME_FILE_OPTION_YES_IF_RELEASE_OR_SONG_MATCH),
    YES_IF_HAS_BASIC_METADATA(TextLabel.RENAME_FILE_OPTION_YES_IF_HAS_METADATA),
    YES_FOR_ALL_SONGS(TextLabel.RENAME_FILE_OPTION_YES),

    ;
    private TextLabel label;

    RenameFileOptions(TextLabel label)
    {
        this.label=label;
    }

    public String getName()
    {
        return label.getMsg();
    }

    public String toString()
    {
        return getName();
    }

    public static List<NameKey> getOptions()
    {
        List<NameKey> options = new ArrayList<NameKey>();
        for(RenameFileOptions next:RenameFileOptions.values())
        {
            options.add(new NameKey(next.ordinal(), next.getName()));
        }
        return options;
    }
}

public class NameKey
{
    private Integer id;
    private String name;

    public NameKey(Integer id, String name)
    {
        this.id =id;
        this.name=name;
    }
    public Integer getId()
    {
        return id;
    }

    public void setId(Integer id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }
}
Paul Taylor
  • 13,411
  • 42
  • 184
  • 351
  • 1
    You have two functional answers but I would **strongly** advise you not to do this. Relying on enum ordinals is a huge mistake. If someone decides to re-organise the order of enum constants (say, to put them into alphabetical order) your code will break in strange and unexpected ways. – Michael Dec 12 '17 at 13:40
  • @Michael, but the only purpose of these classes is to represent possible options and their order, so seems a nice way to do things, what would you recommend instead ? – Paul Taylor Dec 12 '17 at 14:34
  • If it's only the order, there's no need to include `ordinal()` in the HTML output. The risk is, you're running this on two load-balanced servers. You add an option in the middle. During deployment, a user loads a form in which 5=CANCEL. The click it and submit '5' to a new server where 5=ORDER. – slim Dec 12 '17 at 14:46
  • the ordinal represents the option selected, cant use name since that is language dependent (i.e user might be using English or German) – Paul Taylor Dec 12 '17 at 16:46

3 Answers3

5

You should use Class.getEnumConstants() method.

So, middle part of your code should be something like

                each(Arrays.asList(enumClass.getEnumConstants()),
                        next ->
                            iffElse(next.ordinal()==selectedValue,
                                    option(next.name()).attr(Html.SELECTED, Html.SELECTED).withValue(String.valueOf(next.ordinal())),
                                    option(next.name()).withValue(String.valueOf(next.ordinal()))
                            )
                ))

and you should pass Class<? extends Enum<?>> enumClass in method parameters.

Artur Biesiadowski
  • 3,595
  • 2
  • 13
  • 18
  • 1
    Thankyou, that is working but unfortunately Ive realized i need to use getName() not name(), (all my enums of this form take a name parameter that is the display name) so how could I get this working, I could have my enums implements an interface with getName() method but if the method parameter is interface rather than enum I wont have access to ordinal() ectera ? – Paul Taylor Dec 12 '17 at 14:23
  • I don't think there is a direct solution for that, but you could do following: instead of syntax I have provided for parameter, declare type generic for method like `&MyInterface>` and then accept `Class extends T> enumClass` as parameter. `getEnumConstants` will still return just Enum, not extending MyInterface, but you will be able to safely cast it to MyInterface, as nothing else will get accepted from caller site. You can read more about abusing generics in that way here: https://stackoverflow.com/questions/745756/java-generics-wildcarding-with-multiple-classes – Artur Biesiadowski Dec 12 '17 at 14:43
  • Okay, thankyou I understand but as I dont want to be an abuser Im going to take a different approach of adding a static method to the enums themselves that returns a list of namekey pairs, with each containing value of getName() and ordinal() – Paul Taylor Dec 12 '17 at 14:50
3

You can use EnumSet.allOf():

for(MyType t : EnumSet.allOf(MyType.class)) {
    doSomethingWith(t);
}

... or with Java 8 lambdas:

EnumSet.allOf(MyType.class).forEach( 
    t -> doSomethingWith(t)
);

Despite being a Set, EnumSet's documentation states that its iterator yields values "in their natural order (the order in which the enum constants are declared)"


For an arbitarary enum class:

<T extends Enum<T>> void loopAroundEnum(Class<T> enumType) {
    EnumSet.allOf(enumType).forEach(
       val -> doSomethingWith(val) // val is of type T
    );
}

Although the Java habit is to not optimise prematurely, it's worth knowing that an EnumSet is probably more efficient both in speed and in memory consumption than using the MyType[] array returned by MyType.getEnumConstants().

From EnumSet's Javadoc:

Enum sets are represented internally as bit vectors. This representation is extremely compact and efficient. The space and time performance of this class should be good enough to allow its use as a high-quality, typesafe alternative to traditional int-based "bit flags."

slim
  • 40,215
  • 13
  • 94
  • 127
  • thanks but trouble with your solution is you to still specify the Enum (MyType, MyType.class) in the code, therefore it only works with a particular Enum rather than different Enums ? – Paul Taylor Dec 12 '17 at 14:32
  • Ah, hadn't picked up on that requirement. Edited. – slim Dec 12 '17 at 14:37
1

As Enum is the base class, and you need to pass a Class to an enum:

public <E extends Enum<E>> void method(Class<E> type) {
    E[] values = type.getEnumConstants();
}

Of course Class has a test whether it is an enum; it returns null when no enum class.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • 1
    @Michael A bit hasty, corrected. I originally wanted to adapt the original code. – Joop Eggen Dec 12 '17 at 13:36
  • @slim ah, but I especially wanted to offer a type safe usage, restricting the Class parameter to enums. Otherwise a NullPointerException difficulty arises. But `EnumSet.allOf` is a nice way too - though I prefer getEnumConstants. – Joop Eggen Dec 12 '17 at 14:40