3

We have an enum type with quite a lot entries. Each enum value has some boolean (or other) attributes, most have the same value in 90% of cases. We ended up with code like this:

enum Foo {
    A(true, false, false, true),
    B(false, false, false, true),
    C(false, false, false, false);

    final boolean f1;
    final boolean f2;
    final boolean f3;
    final boolean f4;

    Foo(boolean f1, boolean f2, boolean f3, boolean f4) {
        this.f1 = f1;
        this.f2 = f2;
        this.f3 = f3;
        this.f4 = f4;
    }
}

This is a lot of code to write, and hard to read ("what was that boolean`s meaning at 3rd position again?").

This seems to be a use case for annotations:

@Retention(RetentionPolicy.RUNTIME)
@interface Flags {
    boolean f1() default false;
    boolean f2() default false;
    boolean f3() default false;
    boolean f4() default false;
}

enum Foo {
    @Flags(f1=true)
    A,
    @Flags(f4=true)
    B,
    @Flags()
    C;

    final boolean f1;
    final boolean f2;
    final boolean f3;
    final boolean f4;

    Foo(boolean flag1, boolean f2, boolean f3, boolean f4) {
        try {
            Flags flags = Objects.requireNonNull(Foo.class.getField(name()).getAnnotation(Flags.class),
            "Annotation for '"+getClass().getSimpleName()+"."+name()+"' is missing!");
            this.f1 = flags.f1();
            this.f2 = flags.f2();
            this.f3 = flags.f3();
            this.f4 = flags.f4();
        } catch (NoSuchFieldException|SecurityException e) {
          // should not happen
          throw new RuntimeException(e);
        }
    }
}

But can't this be shorter?

enum Foo {
    @Flags(f1=true)
    A,
    @Flags(f4=true)
    B,
    @Flags()
    C;

    final Flags flags;

    Foo(boolean flag1, boolean f2, boolean f3, boolean f4) {
        try {
            this.flags = Objects.requireNonNull(Foo.class.getField(name()).getAnnotation(Flags.class),
            "Annotation for '"+getClass().getSimpleName()+"."+name()+"' is missing!");
        } catch (NoSuchFieldException|SecurityException e) {
          // should not happen
          throw new RuntimeException(e);
        }
    }
}

Of course, now we have to use bar.flags.f1() instead of bar.f1(). An example:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

public class Scratch {

  @Retention(RetentionPolicy.RUNTIME)
  @interface Flags {
    boolean key() default false;

    boolean visible() default true;
  }

  enum Foo {
    @Flags(key = true)
    A,
    @Flags(visible = false)
    B;

    final Flags flags;

    Foo() {
      try {
        this.flags = Objects.requireNonNull(Foo.class.getField(name()).getAnnotation(Flags.class),
            "Annotation for '" + getClass().getSimpleName() + "." + name() + "' is missing!");
      } catch (NoSuchFieldException | SecurityException e) {
        // Sollte nicht vorkopmmen
        throw new RuntimeException(e);
      }
    }
  }

  public static void main(String[] args) {
    for (Foo bar : Foo.values()) {
      System.out.format("%s: key=%-8svisible=%-8s%n", bar.name(), bar.flags.key(), bar.flags.visible());
    }
  }
}

And the output:

A: key=true visible=true

B: key=false visible=false

Apart from the different syntax, is there any reason not to pass the Annotation instance around? Or is there a more elegant solution (that does not involve using a framework)?

3
  • 1
    You're not passing an "annotation instance" around, you're passing around a type, which is pretty much standard Java. I'm not sure I understand the concern. Commented Jan 25, 2017 at 14:09
  • @DaveNewton I pass around Foo.flags which is an object of annotation type. I just don't know if that's the intended use case. Commented Jan 25, 2017 at 14:16
  • Right, you're passing around a type. It's no different than passing around any other type. Commented Jan 25, 2017 at 15:19

2 Answers 2

1

While I'm not answering directly onto your question about annotations, I'm answering on the core of your problem (as you wrote: Or is there a more elegant solution (that does not involve using a framework)?). I wouldn't agree that this case is good place for using annotations.

I would suggest you consider slightly more elegant way of achieving desired pattern:

public enum Foo {
    A(FF.F1, FF.F4),
    B(FF.F4),
    C();

    enum FF {
        F1, F2, F3, F4;
    }

    boolean f1;
    boolean f2;
    boolean f3;
    boolean f4;

    Foo(FF... flags) {
        for (FF ff : flags)
            switch (ff) {
                case F1:
                    f1 = true;
                    break;
                case F2:
                    f2 = true;
                    break;
                case F3:
                    f3 = true;
                    break;
                case F4:
                    f4 = true;
                    break;
            }
    }
}

or even more simple:

public enum Foo {
    A(FF.F1, FF.F4),
    B(FF.F4),
    C();

    enum FF {
        F1, F2, F3, F4;
    }

    boolean[] f = new boolean[FF.values().length];;

    Foo(FF... flags) {
        for (FF ff : flags) {
            f[ff.ordinal()] = true;
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

enums are not required to be immutable:

enum Foo {
  A(){{f1(); f2();}},
  B(){{f4();}},
  C;

  private boolean f1;
  private boolean f2;
  private boolean f3;
  private boolean f4;

  private void f1() {f1 = true;}
  private void f2() {f2 = true;}
  private void f3() {f3 = true;}
  private void f4() {f4 = true;}
}

This more or less gets your intention done, while hiding the details.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.