Iterate through values in @IntDef, @StringDef or any @Def class
Asked Answered
M

4

13

Consider this class:

public class MyClassOfMystery {

    public static final int NO_FLAGS = ~0;
    public static final int FIRST_FLAG = 1;
    public static final int SECOND_FLAG = 1 << 1;
    public static final int THIRD_FLAG = 1 << 2;
    public static final int FOURTH_FLAG = 1 << 3;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, value = {NO_FLAGS, FIRST_FLAG, SECOND_FLAG, THIRD_FLAG, FOURTH_FLAG})
    public @interface MysteryFlags { }

   ... set flags, get flags, and use flags stuff.
}

I have often created something like this, and found that it would be useful to be able to iterate through all flags available in MysteryFlags.

Can I iterate through my values set in MysteryFlags?

This is what I have tried:


This printed ANNOTATION: @java.lang.annotation.Retention(value=SOURCE):

for (Annotation annotation : Flag.class.getAnnotations()) {
   Log.d(TAG, String.format("ANNOTATION: %s", String.valueOf(annotation)));
}

This threw NPE on a null Array access

for (ExtraAction enm : Flag.class.getEnumConstants()) {
   Log.d(TAG, String.format("ENUM: %s", String.valueOf(enm)));
}

These did not print anything out:

for (Field field : Flag.class.getFields()) {
   Log.d(TAG, String.format("FIELD: %s", String.valueOf(field)));
}

and

for (Class<?> aClass : ExtraAction.class.getClasses()) {
        Log.d(TAG, String.format("CLASS: %s", String.valueOf(aClass)));
}

I know I can just add the values to an array and iterate through that, but that requires storing another array. It is what I have done, but still wonder if there is a better way.

Multicellular answered 22/8, 2015 at 20:18 Comment(2)
Shouldn't that be NO_FLAGS = 0?Cleft
@Cleft No. Flags indicate bitwise operations. NO_FLAGS will clear all flags - hence ~Multicellular
A
9

I don't think you'll be able to query it like that at runtime. Your @MysterFlags annotation has a retention policy of SOURCE, which means it will be discarded by the compiler. Further, the @IntDef annotation has a retention policy of CLASS, which means it makes it through compile, but won't make it to runtime. That's why you are only seeing the @Retention annotation in your first loop (that annotation has a retention policy of RUNTIME).

Abeabeam answered 22/8, 2015 at 20:56 Comment(2)
Could I iterate through if I change the retention policy to runtime?Multicellular
If you're trying to look at the values for @IntDef, then I don't think so. You wouldn't be able to change the retention policy of that annotation.Abeabeam
C
3

A compromise can be made if we declare our fields within the @interface itself.

@Retention(RetentionPolicy.SOURCE)
@IntDef({MysteryFlags.NO_FLAGS, MysteryFlags.FIRST_FLAG, MysteryFlags.SECOND_FLAG, MysteryFlags.THIRD_FLAG, MysteryFlags.FOURTH_FLAG})
public @interface MysteryFlags {

    // Note that all fields declared in an interface are implicitly public static final
    int NO_FLAGS = ~0;
    int FIRST_FLAG = 1;
    int SECOND_FLAG = 1 << 1;
    int THIRD_FLAG = 1 << 2;
    int FOURTH_FLAG = 1 << 3;
}

When calling getFields() on MisteryFlags.class, all fields declared in the annotation are returned.

However, this means that any fields in the @interface that are not defined within the @IntDef will also be returned. IMO, this can work great if implemented by following a strict protocol.

Cowell answered 4/12, 2019 at 15:58 Comment(0)
T
0

Well, this might be a bit old now - but I had a similar problem and the solution that I've found was:

MysteryFlags.class.getDeclaredFields()

It will return all the definitions declared.

Tracheostomy answered 15/4, 2019 at 14:53 Comment(1)
This does not work if the fields are declared in MyClassOfMystery as they are in the OP's exampleCowell
E
0

This is how you can iterate over. Don't forget about Retention RUNTIME!

class MyClassOfMystery {

    init {
        //How to iterate over Mysteries companion object
        Mysteries::class.java.declaredFields.map { it[0] }.filterIsInstance<String>()
    }

    @Retention(AnnotationRetention.RUNTIME)
    @StringDef(KING_ARTHUR, BIMINI_ROAD, NAZCA_LINES, STONEHENGE)
    annotation class Mysteries {
        companion object {
            const val KING_ARTHUR = "king_arthur"
            const val BIMINI_ROAD = "bimini_road"
            const val NAZCA_LINES = "nazca_lines"
            const val STONEHENGE = "stonehenge"
        }
    }
}

enter image description here

Enliven answered 22/7, 2022 at 12:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.