Dror Cohen
Dror Cohen

Reputation: 6841

Enum relating to a string array that was read from file

I've received a working code (in Java, 1.7) that does the following: load an array of strings (a list of blood test names) from a file into a string array member (using Properties and FileInputStream). The file can change the strings but the meaning stays the same (for example: a test can be called "abc" and in another run it is called "zzz"). I've got an enum class that enumerates the test names. The enum strings aren't the same as the inputted strings (since the latter can change).

file bloodtest.names contains:

bloodTestNames=abc;def;123;

code:

public enum BloodTestNames {
AAA,BBB,CCC;    
}

Properties props = new Properties();
FileInputStream fis = new FileInputStream("bloodtest.names");
props.load(fis);
String testName[]=props.getProperty("bloodTestNames").toString().split(";");

Now to the questions: Question 1: I need to return the string that was set in the file when I know the test name (for instance: return "def" for value BBB). What's the best of doing that?

the best way I've come up with is:

return testName[BloodTestNames.BBB.ordinal()]

Question 2: if BBB is not known in compile time - how do I accomplish the same target?

Three points: * I'm a veteran at C but a newbie with Java. Any Do's and Don't are welcome. Assume my Java knowledge is zero. * I don't total re-factoring is that's what's needed here. * I've probably forgot to mention important details, please ask and I'll feel the missing gaps

Upvotes: 1

Views: 1405

Answers (3)

Costi Ciudatu
Costi Ciudatu

Reputation: 38255

I'll first assume you do need enum constants for modeling this use-case because you have some sort of specific code to be executed for each kind of blood test (otherwise, a simple set of strings would be enough and more flexible, since you don't need to know the number of tests upfront or care about their names).


Q1: Since Java enums are a little more than a sequence of values, you can make full use of their object oriented nature.

public enum BloodTest {
    AAA, BBB, CCC;

    private static String[] names;

    public static void setNames(String[] names) {
        if (BloodTest.names != null)
            throw new IllegalStateException("You can only set the names once");
        if (names.length != values().length)
            throw new IllegalArgumentException("Wrong number of names");
        BloodTest.names = names;
    }

    @Override
    public String toString() {
        return names[ordinal()];
    }
}

Now all you need to do is to initialize your enum by calling BloodTest.setNames(namesFromConfiguration) and then you can get the string representation of each constant by calling the standard toString() method on it: BloodTest.BBB.toString().

Since the initial assumption was that you have some specific logic for each of the test types, I would suggest that logic (as well as the required properties) will also be encapsulated in the enum itself or the enum constants; e.g.:

public enum BloodTest {
    AAA(10) {
        @Override
        public boolean isRequired(MedicalRecord medicalRecord) {
            return medicalRecord.includes("someDisease");
        }
    },
    BBB(15) {
        @Override
        public boolean isRequired(MedicalRecord medicalRecord) {
            return ! medicalRecord.hasTakenBloodTestsLately();
        }
    },
    CCC(20) { // ... also implements the abstract method and so on

    private final int threshold;

    private BloodTest(int threshold) {
        this.threshold = threshold;
    }

    public boolean hasPassed(int value) {
        return value <= threshold;
    }

    public abstract boolean isRequired(MedicalRecord medicalRecord);

    // ... same as above
}

Now, once you get a reference to some BloodTest, you can check whether that specific test passed by invoking the corresponding method without switching and having the logic spread around the client code:

BloodTest bloodTest = BloodTest.valueOf(someString); // someString can be "AAA", "BBB" or "CCC"
// no matter which constant this is, you use it as an object and rely on polymorphism
if (bloodTest.hasPassed(someValue)) { // ... do something

Q2: Your question 2 kind of "questions" my initial assumption regarding your actual need for an enum. If there's a chance you'll need to dynamically handle blood tests that you don't know about yet, then you can't use an enum.

In other words, if your code does not have any switch or if/else if blocks to handle each blood test, an enum is a really bad choice for your use case.

However, if it does, than I'd recommend refactoring the code to include the logic in the enum itself as in the above example, rather than in switch/if blocks; moreover, if your switch has a default case (or your if has a final else block), this can still be modeled in the enum itself, for instance by adding a DEFAULT constant as a fallback.

Upvotes: 1

Bohemian
Bohemian

Reputation: 425258

Make the whole thing settings driven: Add a statuc method to load in settings of what string maps to what enum and add a factory method that uses these settings:

public enum BloodTestNames {
    AAA,BBB,CCC; 
    private static Map<String, BloodTestNames> map = new HashMap<String, BloodTestNames>();

    public static void addAlias(String alias, String name) {
        map.put(alias, valueOf(name));
    }

    public static BloodTestNames getByAluas(String alias) {
        if (map.containsKey(alias))
            return map.get(alias);
        // own name assumed to be mapped
        return valueOf(alias);
    }
}

On startup, repeatedly call BloodTestNames.addAlias() based on some settings file to load the mappings.

When you're reading the saved file, use BloodTestNames.getByAlias() to return the enum for a given string value.


You would do well to name your class in the singular, and drop "Name", ie BloodTest - name the class for what each enum is (all enums have a "name" which is the coded instance name).

Upvotes: 1

cl-r
cl-r

Reputation: 1264

A short extract from one of my enum class :

public enum TypesStructurelsE {
    SOURCE("SRC"),
    COLONNE("COL");

    private String code;

    TypesStructurelsE(final String code1) {
        code = code1;
    }


    /** @return String */
    public String getCode() {
        return code;
    }

    public void setCode(final String newCode) {
        code = newCode;
    }
}

. . In other class

if(TypesStructurelsE.SOURCE.getCode().equal(testName[i])){ // can be "COL" or "SRC" 
//
;
}

... changing value :

TypesStructurelsE.SOURCE.setCode("SOURCE_NEW");

So, if your properties file change, you have just to compile with the new symbole (SRC --> SOURCE) no more

Upvotes: 0

Related Questions