projectlombok / lombok

Very spicy additions to the Java programming language.
https://projectlombok.org/
Other
12.89k stars 2.39k forks source link

[FEATURE] @EnumNameConstants #2731

Open daniel-shuy opened 3 years ago

daniel-shuy commented 3 years ago

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

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 commented 3 years ago

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 commented 3 years ago

@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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

@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 :smile:

daniel-shuy commented 3 years ago

@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 commented 3 years ago

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 commented 3 years ago

@ravidesai47 oh wow, I'm surprised we have the same use case πŸ˜„

ravidesai47 commented 3 years ago

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

ravidesai47 commented 3 years ago

@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 commented 2 years ago

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

neseleznev commented 1 year ago

+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 commented 1 year ago

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

xerZV commented 1 year ago

+1

LuisEscamillaMartin commented 1 year ago

+1

hitakashio commented 1 year ago

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 commented 1 year ago

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! 🀯