Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] @EnumNameConstants #2731

Open
daniel-shuy opened this issue Jan 30, 2021 · 17 comments
Open

[FEATURE] @EnumNameConstants #2731

daniel-shuy opened this issue Jan 30, 2021 · 17 comments

Comments

@daniel-shuy
Copy link
Contributor

daniel-shuy commented Jan 30, 2021

Describe the feature
Currently, there are 2 main ways to define String constants in Java:

  • enums
  • static final constants

Enums can be enumerated (using Enum#values(), EnumSet, etc), and provides better typesafety (enum method parameters, exhaustive switch case with Java 13 Enhanced Switch), but the main issue is that it cannot be used as a String constant in annotation parameters (because Enum#name() is not a compile time constant).

WIth static final constants, for Android, some of the typesafety can be supplemented with Android's TypeDef annotations (eg. @StringDef), but for non-Android environments, there is no alternative. Manually definiting a Collection to enumerate static final constants is also error prone. A HashSet/HashMap is also slower than an EnumSet/EnumMap.

I suggest to create an annotation called @EnumNameConstants (inspired by @FieldNameConstants), that when annotated on an enum, will generate an inner type with string constants of the name of each enum value.
eg.

@EnumNameConstants
public enum Level {
    LOW,
    MEDIUM,
    HIGH,
    ;
}

should generate

public enum Level {
    LOW,
    MEDIUM,
    HIGH,
    ;

    public static final class Fields {
        public static final String LOW = "LOW";
        public static final String MEDIUM = "MEDIUM";
        public static final String HIGH = "HIGH";
    }
}

This will provide all the advantages of using enums, while removing its greatest limitation.

Describe the target audience
Example use case:

public enum Permissions {
    READ_USERS,
    WRITE_USERS,
    ;
}

@Test
public void testUserService() {
    val userService = new UserService();
    userService.createUserWithPermissions(Permissions.values());
}

@Test
@WithMockUser(roles = Permissions.Fields.READ_USERS)
public void test() {
    // ...
}

Additional context

@Rawi01
Copy link
Collaborator

Rawi01 commented Jan 30, 2021

You can use @FieldNameConstants for this:

@FieldNameConstants
public enum Permissions {
    @FieldNameConstants.Include READ_USERS,
    @FieldNameConstants.Include WRITE_USERS,
    ;
}

I don't know if this is intended, I was acutally supprised that it seems to be okay to add annotations to enum values.


In general I think that it would be way better if you could define your own annotation that accepts an enum attribute (@YourWithMockUser(roles = Permissions.READ_USERS)) and lombok maps that one to the string based annotation (@WithMockUser(roles = "READ_USERS")). This might be an extension to the already quite complex meta-annotation feature request.

@daniel-shuy
Copy link
Contributor Author

daniel-shuy commented Feb 1, 2021

@Rawi01 I just tried that with the latest Lombok version (1.18.18) and it didn't work for me (I can annotate an enum value with @FieldNameConstants.Include, but it won't generate a constant for it). What version did you try with?

@pkordis-r7
Copy link

In general I think that it would be way better if you could define your own annotation that accepts an enum attribute...

I don't think that javac allows enums as annotation attributes, in fact it will only allow constants

@ravidesai47
Copy link

This is one of the important feature for our projects for maintainability. As for all enums we do create which is requested by requestor. I would request lombok team to build this feature.

@Rawi01
Copy link
Collaborator

Rawi01 commented Apr 28, 2021

@daniel-shuy I just tried it again using 1.18.20 and it works in Eclipse and in javac it but it is marked as error in IntelliJ. It still works if you simply run it. Do you think it would be sufficient if we simply extend @FieldNameConstants to also pick up the enum values?
@pkordis-r7 You can use enums in annotations: JLS
@ravidesai47 Can you share an example why you need this feature? So far there only is @WithMockUser and some usage examples would be nice and might increase the prority of this feature request 😄

@daniel-shuy
Copy link
Contributor Author

daniel-shuy commented Apr 29, 2021

@Rawi01

I just tried it again using 1.18.20 and it works in Eclipse and in javac it but it is marked as error in IntelliJ. It still works if you simply run it.

Interesting, looks like its an issue with the lombok-intellij-plugin. But then again, as you previously mentioned, it seems to be an unintended feature.

Do you think it would be sufficient if we simply extend @FieldNameConstants to also pick up the enum values?

Sure, whatever works. Maybe @FieldNameConstants can be extended with a flag to include static fields (which includes enum values)?

Can you share an example why you need this feature? So far there only is @WithMockUser and some usage examples would be nice and might increase the prority of this feature request 😄

I don't know about @ravidesai47's use case, but I can add one more if it might help increase the priority of this request 😝
Another example is when using Jackson's polymorphic serialization/deserialization with logical type names (JsonTypeInfo.Id.NAME), eg.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes( {
        @JsonSubTypes.Type(value = Automobile.class),
        @JsonSubTypes.Type(value = Plane.class),
        @JsonSubTypes.Type(value = Ship.class),
})
@Data
abstract class Vehicle {
    private Double latitude;
    private Double longitude;
}

enum VehicleType {
    AUTOMOBILE,
    PLANE,
    SHIP,
    ;
}

@JsonTypeName(VehicleType.AUTOMOBILE)  // does not compile!
class Automobile extends Vehicle {
    private Integer wheelCount;
}

This will cause the Automobile class to be serialized with a "@type": "AUTOMOBILE" field, which can then be used by a typed client (eg. Java, TypeScript, etc) to correctly deserialize it to the right class.

Why is there a need for an enum? There is currently no way to get all subclasses of Vehicle. It may eventually be possible if Java's sealed classes (added in Java 15) copies Kotlin's KClass#sealedSubclasses, but I'm not holding my breath (this may be another feature that Lombok could implement). Enum#values() provides us a way to get all VehicleTypes.

@ravidesai47
Copy link

ravidesai47 commented Apr 29, 2021

My use-case is related to jackson json polymorphism. We have lot of json polymorphed objects which has a polymorphism based on Enum which requires this feature to generated auto-generated name fields. We create enum name text fields in all such enums to use them in @JsonSubTypes annotation. It becomes maintenance headache as each time new enum is added, removed or modified we need to change the respective TEXT field. Enum value has a significance in object hence we can't replace name based strategy to any other strategy. If client is written in Angular, react it might not understand java types either.

Example:

@JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY, property = "vehicleType", visible = true)
@JsonSubTypes( {
        @JsonSubTypes.Type(value = Automobile.class, name = AUTOMOBILE_TEXT),
        @JsonSubTypes.Type(value = Plane.class, name = PLANE_TEXT),
        @JsonSubTypes.Type(value = Ship.class, name = SHIP_TEXT),
})
@Data
abstract class Vehicle {
    private VehicleType vehicleType;
    private Double latitude;
    private Double longitude;
}

enum VehicleType {
    AUTOMOBILE,
    PLANE,
    SHIP,
    ;

   public static final String AUTOMOBILE_TEXT = "AUTOMOBILE";
   public static final String PLANE_TEXT = "PLANE";
   public static final String SHIP_TEXT = "SHIP";
}

Hope this helps in increasing priority of this feature.

I am sure many more developers and organizations must be interested in this feature as it adds a lot of value to Json Polymorphed code for sure. I am not sure about other use-cases. As I have came across the mentioned use-case only till now. This use-case is valid across every organization I have worked for.

@daniel-shuy
Copy link
Contributor Author

@ravidesai47 oh wow, I'm surprised we have the same use case 😄

@ravidesai47
Copy link

@daniel-shuy I think this is very common use-case in all complex systems.

@ravidesai47
Copy link

@daniel-shuy I think it needs to be discussed in https://groups.google.com/g/project-lombok forum. Someone from team project lombok would be able to help there increasing priority of this feature.

@alexandrenavarro
Copy link

+1 I have exactly the same use case with jackson json polymorphism (and some on others annotations).

@neseleznev
Copy link

+1 This feature is needed to reference particular enum value in OpenApi descriptions as they're also annotations-based Strings with same limitations

    @Schema(title = "Your enum property",
            description = """
                Yada-yada. \s
                The property appeared on Jan 1, 2023. \s
                For the legacy entities, special value \s
            """ + YourEnumType.LEGACY.name() + " is returned",
            required = true,
            example = YourEnumType.USEFUL_EXAMPLE_VALUE.name()) // NB! also useful!
    @NotNull
    YourEnumType yourEnumType;

@delyand
Copy link

delyand commented Apr 13, 2023

+1 we have a very similar use case. Does this feature have a shot?

@xerZV
Copy link

xerZV commented Apr 28, 2023

+1

1 similar comment
@LuisEscamillaMartin
Copy link

+1

@hitakashio
Copy link

hitakashio commented Jul 10, 2023

The solution, @Rawi01 suggested can be used in IntelliJ if you set onlyExplicitlyIncluded=true:

@FieldNameConstants(onlyExplicitlyIncluded = true)
public enum Role{
        @FieldNameConstants.Include ADMIN,
        @FieldNameConstants.Include USER
}

private String foo(){
        return Role.Fields.USER;
}

@daniel-shuy
Copy link
Contributor Author

The solution, @Rawi01 suggested can be used in IntelliJ if you set onlyExplicitlyIncluded=true:

@FieldNameConstants(onlyExplicitlyIncluded = true)
public enum Role{
        @FieldNameConstants.Include ADMIN,
        @FieldNameConstants.Include USER
}

private String foo(){
        return Role.Fields.USER;
}

It works! 🤯

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants