Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create an extended "enum" with runtime attributes

Tags:

java

enums

class

I want to simplify the selection of a particular predefined object.

I currently have an enum defined as

ArtworkType { Poster, Banner, Other }

And I want to add attributes to those ArtworkTypes so that I can use them in code elsewhere. The attributes of the ArtworkTypes are either pre-defined static labels or populated from an external configuration file that is populated into a Properties() class.

Ideally I want to do something as simple as

ArtworkType.Poster.getWidth();

If I have to use a final class, I think it's going to be more complicated having to use something like

ArtworkType.getWidth(TypeEnum.Poster);


EDIT: Thanks for the answers below, I conclude that whilst I can do it with an Enum, it's probably better to use an external class (e.g. ArtworkUtil) to retrieve the attributes I'm after.

This is the sample enum code I've created so far (error checking omitted):

public enum ArtworkType {
    Poster("poster"), Banner("banner"), Other("other");

    private String type;
    private Dimension dimension;

    private ArtworkType(String type) {
        this.type = type;
        this.dimension = new Dimension(Properties.getProperty("width."+type), Properties.getProperty("height."+type);
    }

    public Dimension getDimension() {
        return dimension;
    }
}

While I appreciate this is against the principles of strict Enums, as the values that are associated with the Enum are static (for the duration of the application) it might be the lesser of two evils.

The only other approach that I can think of is to create an "ArtworkUtil" class that creates a collection and populates all the required attributes into an object and stores that in the collection.

Accessing that class in the code would make it a lot more unreadable (unless I'm missing something?)

like image 990
Omertron Avatar asked Dec 07 '22 23:12

Omertron


1 Answers

Enums are compile-time constants. You can not initialize them from property files.

They can however have constructors, methods and fields. They can also have basic method implementations in the main body that are overwritten in the individual enum entries.

public enum Shape{
    SQUARE(10),
    RECTANGLE(10, 15),
    CIRCLE(10){
        @Override
        public double getArea(){
            return Math.PI * Math.pow(((double) getWidth()) / 2, 2);
        }
    },
    OVAL(10, 15){
        @Override
        public double getArea(){
            return Math.PI * (getWidth()) / 2 * (getHeight()) / 2;
        }
    };

    private Shape(final int dim){ this(dim, dim); }
    private Shape(final int width, final int height){
        this.width = width; this.height = height;
    }

    private final int width;
    private final int height;

    public double getArea(){ return width * height; }

    public final int getWidth(){ return width; }

    public final int getHeight(){ return height; }

}

Test code:

public static void main(final String[] args){
    for(final Shape shape : Shape.values()){
        System.out.printf("Shape: %s, area: %1.2f\n", shape,
            shape.getArea());
    }
}

Output:

Shape: SQUARE, area: 100.00
Shape: RECTANGLE, area: 150.00
Shape: CIRCLE, area: 78.54
Shape: OVAL, area: 117.81


Dealing with Compile-Time restrictions

Enums are compile-time constants, so you can not initialize them using non-constant values, which means that you can't do this:

SQUARE(SomeClass.getSquareValue())

So you basically have three options:

  1. Generate the enum automatically from your properties file

    Use a build tool like Maven and have some code generate an enum file for you, converting this format:

    ENUMNAME=property.value
    

    into this enum entry:

    ENUMNAME("property.value")
    

    Add the generated enum .java to your compile sources. From the Java point of view, this is the cleanest approach, as you have absolute compile-time safety. The problem is: you need to re-compile every time the property file changes.

  2. Lazy-initialize the enum (argh)

    Initialize the enum items with property keys, and when the enum's method is first called, look up the property from the classpath, caching it for further use. This is an awful hack, but it is useful sometimes.

  3. Pass all values into the enums from the outside, using them as strategy:

    Example: this enum looks up System properties with a different prefix per enum entry.

    public enum Lookup{
         ADDRESS("$1".address),
         NAME("$1".name);
         private final String pattern;
         private Lookup lookup(String pattern){
             this.pattern=pattern;
         }
         public final String lookupProperty(String input){
             return System.getProperties().get(
                 this.pattern.replace("$1",input)
             );
         }
     }
    
like image 55
Sean Patrick Floyd Avatar answered Dec 09 '22 12:12

Sean Patrick Floyd