4

I'm working on a project, and my supervisors want me to be able to get the values of enum constants via an annotation processor. For example from the following enum definition:

public enum Animal {
    LION(5),
    GIRAFFE(7),
    ELEPHANT(2),

    private int value;

    Animal(int value) {
        this.value = value;
    }

    public int Value() {
        return value;
    }
}

They want me to compile an array of [5, 7, 2].

Note that because I am working within an annotation processor, I am using Element based reflection (not Class based reflection).

My reading of the VariableElement documentation leads me to believe this is impossible.

Note that not all final fields will have constant values. In particular, enum constants are not considered to be compile-time constants.

Does anyone know of a way to get this working?

Thank you for taking the time to read this! --Beka

Beks_Omega
  • 404
  • 1
  • 5
  • 13
  • You could iterate through the children of that enum and if one is a [VariableElement](https://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/VariableElement.html), you can get its value – user Jun 02 '20 at 22:16
  • Thank you for your answer! But I'm confused, doesn't the documentation of VariableElement say you cannot do this with enum constants? (see quote in OP) How do I use VariableElement to get the value? – Beks_Omega Jun 02 '20 at 22:59
  • It also says "Represents a field, enum constant, ..." so I thought it'd be possible – user Jun 02 '20 at 23:01
  • True! The documentation seems a bit confusing here :/ – Beks_Omega Jun 02 '20 at 23:18
  • I think what they mean is that if you have a field `final MyEnum someField = MyEnum.SOME_CONSTANT`, that won't be considered a constant – user Jun 02 '20 at 23:19
  • 1
    The problem is not that the enum constants are not compile-time constants, as you still know their value at compile-time, e.g. `Animal.GIRAFFE` is known to hold an object of type `Animal`, with the `name() == "GIRAFFE"` and `ordinal() == 1`. The problem is that you want to get the value of the field `Animal.value`, i.e. `Animal.GIRAFFE.value` which is assigned in the constructor. – Holger Jun 04 '20 at 10:22
  • Ok cool! Thank you @Holger That's how I read it as well, but I wanted to check to see if the wider world had any ideas. – Beks_Omega Jun 04 '20 at 14:03
  • 2
    It can be done using the compiler tree api https://stackoverflow.com/questions/6373145/accessing-source-code-from-java-annotation-processor – Odysseas Jun 17 '20 at 07:04
  • @Holger I have similar requirement. I just want to understand if there is no way get the value during annotation processing as they are not compile time constants ? – The_Java_Guy Mar 01 '23 at 18:59

1 Answers1

1

What we ended up doing for this project is we compiled all of the enums before running the annotation processor. If a class has already been compiled before you run the annotation processor, it is possible to access information about it using Class based reflection.

Compiling the enums first was workable in this case because the enums' classes were passed to annotations on other classes (the enums are used to define some dropdowns).

Here is the code used to get the names of enum constants and their values. Where optionElem is the Element representing the enum class.

private <E extends Enum<E>> boolean tryAddOptionList(Element optionElem) {
    String className = optionElem.asType().toString();

    // Get the class.
    Class clazz = null;
    try {
      clazz = Class.forName(className);
    } catch (ClassNotFoundException e) {
      throw new IllegalArgumentException("OptionList Class: " + className + " is not available. " +
        "Make sure that it is available to the compiler.");
    }
    if (clazz == null) {
      return false;
    }

    // Get the getValue method.
    java.lang.reflect.Method getValueMethod = null;
    try {
      getValueMethod = clazz.getDeclaredMethod("getValue", new Class[] {});
    } catch (NoSuchMethodException e) {
      throw new IllegalArgumentException("Class: " + className + " must have a getValue() method.");
    }
    if (getValueMethod == null) {
      return false;
    }

    // Create a map of enum const names -> values.
    Map<String, String> namesToValues = Maps.newTreeMap();
    Object[] constants = clazz.getEnumConstants();
    for (Object constant : clazz.getEnumConstants()) {
        try {
          E enumConst = (E) constant;
          namesToValues.put(
            enumConst.name(),
            getValueMethod.invoke(enumConst, new Object [] {}).toString());
        } catch (Exception e) {}
    }
    // etc...
}

Here is the full pull request if you would like more context. The actually enum parsing is handled by the ComponentProcessor file.

Beks_Omega
  • 404
  • 1
  • 5
  • 13
  • Could you share complete example, as i have similar requirement? – Prakash Panjwani Dec 17 '20 at 13:23
  • Hmm I'm not sure how I could make the example more complete @prakash.panjwani I've provided the code for processing, as well as the link to the pull request where I implement the complete system =) But if you have any specific questions I can do my best to answer them! – Beks_Omega Dec 17 '20 at 17:51