Add `@EnumHelpers`
Description
Implement @EnumHelpers annotation and the corresponsing annotation processor to generate boilerplate enum methods.
Problem
While Java's enum is a powerful mechanism for describing varios constructions, it has a number of negative aspects which make it not that helpful.
The biggest problem is static E @NotNull [] values() method which is required to always return a fresh copy of the array due to array mutability although it's usually needed for iteration.
Another serious design problem (especially considering padla's philosophy) is inability to do try-match operation, i.e. to try to find an enum constant by a string name alternatively fallin back to null or default value etc.
This PR addresses this by provifing a @EnumHelpers annotation causing the generation of ES utility class for enum E.
Goals
- [x] Add
@EnumHelperallowing generation of:- [x]
@NotNull Stream<@NotNull E> stream() - [x]
@NotNull @Unmodifiable List<@NotNull E> values() - [ ]
@Nullable E match(final @NotNull String name)and similar ones
- [x]
- [x] implement annotation-processor to generate the helper class
Example
Given
@EnumHelpers
public enum Foo {
A, B, C
}
the following should be generated:
@Generated
public final class Foos {
private static final @NotNull Foo @NotNull [] VALUES_ARRAY
= Foo.values(); // may be used for faster iterations
private static final @NotNull List<@NotNull Foo> VALUE_LIST
= Collections.unmodifiableList(Arrays.asList(VALUES_ARRAY));
private Foos() {
throw new AssertionError("Foos is an utility class and thus should never be instantiated");
}
public static @NotNull Stream<@NotNull Foo> stream() {
return Arrays.stream(VALUES_ARRAY);
}
public static @NotNull @Unmodifiable List<@NotNull Foo> values() {
return VALUES_LIST;
}
public static @Nullable Foo match(final @NotNull String name) {
if (name == null) throw new NullPointerException("name is null");
switch (name) {
case "A": return A;
case "B": return B;
case "C": return C;
default: return null;
}
}
public static Foo match(final @NotNull String name, final Foo defaultValue) {
if (name == null) throw new NullPointerException("name is null");
switch (name) {
case "A": return A;
case "B": return B;
case "C": return C;
default: return defaultValue;
}
}
// note: possibly worth specialized lambda
public static @Nullable Foo match(final @NotNull String name, final @NotNull Supplier<Foo> defaultValueSupplier) {
if (name == null) throw new NullPointerException("name is null");
if (defaultValueSupplier== null) throw new NullPointerException("defaultValueSupplieris null");
switch (foo) {
case "A": return A;
case "B": return B;
case "C": return C;
default: return defaultValueSupplier.get();
}
}
// ...
}
Notes
Generated class should net rely on Lombok as it may be not used by annotation users.
Subjects to discuss:
- Documenting annotation should be generated if possible(?) but it is not clear whether some eaxct one (
org.jetbrains) should be used or custom (via an annotation parameter).
Suggestions:
- use following naming:
Foos.stream(),Foos.list(),Foos.array() - use
List.of()instead ofCollections.unmodifiableList(Arrays.asList())for Java 9+ - make Foos.match generation optional
- add additional annotation to generate match-like methods for finding by field:
@RequiredArgsConstructor
@Getter
enum Foo {
FOO_BAR("foo-bar"),
BAR_BAZ("bar-baz");
@EnumHelpers.Finder
private final String value;
}
class Foos {
public static Foo fromValue(String value) {
// some optimal finding logic
// for example:
// 1) use HashMap for big enums and array-foreach for small enums
// 2) switch-case if enum field is compile-time constant
}
}
- add additional annotation to generate enum name constants and field constants:
@RequiredArgsConstructor
@Getter
@EnumHelpers.NameConstants
enum Foo {
FOO_BAR("foo-bar"),
BAR_BAZ("bar-baz");
@EnumHelpers.FieldConstants
private final String value;
}
class Foos {
public static final String FOO_BAR_NAME = "FOO_BAR";
public static final String BAR_BAZ_NAME = "BAR_BAZ";
public static final String FOO_BAR_VALUE = "foo-bar";
public static final String BAR_BAZ_VALUE = "bar-baz";
}