Question

I'm trying to load some log messages from a configuration file, but I'd still like to refer to their names via an enum rather than opening myself up to typographcial errors when typing their names out as Strings. So here's my setup:

public class Log {

    private static final Logger LOGGER = Logger.getLogger(Log.class);
    private static final String MESSAGES_FILE_PATH = "conf/log_message.conf";

    private static final Properties MESSAGES = new Properties();
    static {
        try {
            MESSAGES.load(new FileInputStream(new File(MESSAGES_FILE_PATH)));
        }
        catch(IOException ioe) {
            LOGGER.fatal("Unable to load log messages from file: " + MESSAGES_FILE_PATH, ioe);
        }
    }

    public enum Message {

        //Main
        PROGRAM_EXIT,
        THREAD_INTERRUPTED,
        FATAL_TERMINATING_ERROR,
        SHUTDOWN_HOOK_EXCEPTION,
        IO_READ_ATTEMPT,
        IO_READ_FAILURE,
        IO_WRITE_ATTEMPT,
        IO_WRITE_FAILURE,
        IOEXCEPTION,

        //...

        private final String text;

        private Message() {
            text = MESSAGES.getProperty(name());
        }

        //...
    }
}

My concern is that there might be some fringe scenario where the static initializer in Log doesn't run before the enums are initialized. I've tested the code and so far it works fine, and, logically, I don't see how the static initializer could ever not run first (since references to Message have to go through Log, e.g. Log.Message.IOEXCEPTION). Still, I'm a little uneasy about the setup and don't want to leave any possible loopholes for the app crashing. So, is this safe?

Was it helpful?

Solution

Your concern is that the enums would be somehow accessed without static init of its outer class but this is impossible. The enum accesses an outer class member:

private Message() {
    text = MESSAGES.getProperty(name());
}   //        ^ static field of Log

Accessing MESSAGES will cause Log to be loaded and initialized if it's not already.

"I don't see how the static initializer could ever not run first (since references to Message have to go through Log, e.g. Log.Message.IOEXCEPTION)"

Accessing the nested class through the outer class name doesn't cause the outer class to be initialized.

For the record, here is the list of what causes a class to be initialized (JLS 12.4.1):

  • T is a class and an instance of T is created.

  • T is a class and a static method declared by T is invoked.

  • A static field declared by T is assigned.

  • A static field declared by T is used and the field is not a constant variable (§4.12.4).

  • T is a top level class (§7.6), and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed.

(Emboldened is the one causing Log to be initialized.)

And although MESSAGES is static and final, it's not a constant variable in the eyes of the JLS. Constant variables are defined in final Variables as the following:

A variable of primitive type or type String, that is final and initialized with a compile-time constant expression...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top