6

I have a bunch of constants throughout my code for various adjustable properties of my system. I'm moving all of them to a central .properties file. My current solution is to have a single Properties.java which statically loads the .properties file and exposes various getter methods like this:

public class Properties {
    private static final String FILE_NAME = "myfile.properties";
    private static final java.util.Properties props;

    static {
        InputStream in = Properties.class.getClassLoader().getResourceAsStream(
                FILE_NAME);
        props = new java.util.Properties();
        try {
            props.load(in);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static String getString(Class<?> cls, String key) {
        return props.getProperty(cls.getName() + '.' + key);
    }

    public static int getInteger(Class<?> cls, String key) {
        return Integer.parseInt(getString(cls, key));
    }

    public static double getDouble(Class<?> cls, String key) {
        return Double.parseDouble(getString(cls, key));
    }
}

The only problem with that is that for every constant that I get from this file, I have some boilerplate:

private final static int MY_CONSTANT = Properties.getInteger(
    ThisClass.class, "MY_CONSTANT");

I don't think I want to use Spring or the like as that seems like even more boilerplae. I was hoping to use a custom annotation to solve the issue. I found this tutorial, but I can't really sort out how to get the functionality that I want out of the annotation processing. The Java docs were even less helpful. This should be a thing I should be able to do at compile time, though. I know the names of the class and field.

What I'm thinking is something like this:

@MyAnnotation
private static final int MY_CONSTANT;

Anyone know how I would go about doing this or at least best practices for what I want to do?

maaartinus
  • 44,714
  • 32
  • 161
  • 320
Ellis Michael
  • 962
  • 1
  • 8
  • 14
  • Why do you think its loads of boiler plate code? wont adding annotation would add to it ? – SMA Mar 22 '15 at 16:56
  • Are those really constants? Or more like configuration items? If they're configuration, is it really needed to write them as constants ? Because the `final` modifier would lose its meaning (I'm not even sure that reflection can assign a value to a `final` field). If they're only configuration, also, why wouldn't you use a configuration framework to ease your task (like [commons-configuration](https://commons.apache.org/proper/commons-configuration/) or [typesafe's config](https://github.com/typesafehub/config))? If they're constants, why the need to be able to "modify" them at each run? – Olivier Grégoire Mar 22 '15 at 17:05
  • Also, you don't close your `InputStream` in your `try-catch-finally` block. You should try and do that. – Olivier Grégoire Mar 22 '15 at 17:07
  • They are constants since they're constant for the lifetime of the system, but I would like to vary them for different experiments I'm running. Assigning to final shouldn't be a problem as annotation processing runs at compile time. Closing `InputStream` is a good point (though not really relevant). – Ellis Michael Mar 22 '15 at 17:17
  • Check [Constants and annotations](http://stackoverflow.com/questions/1510319) for computed values in compile-time annotation processing. – Olivier Grégoire Mar 22 '15 at 17:21
  • Do you want automatically fill with value from file `static final` constants using only special annotations and these constants names? It is impossible in java. – Andremoniy Mar 22 '15 at 18:07
  • Based on what I've seen things like Project Lombok do, I **really** doubt it's impossible. I would even be happy with something that would just insert the appropriate `Properties.getX(ClassName.class, "FIELD_NAME")` – Ellis Michael Mar 22 '15 at 18:18

2 Answers2

2

First of all, you shouldn't do it. It's practical, but too hacky and if you ever want to write a test using different settings, you'll run into problems. Moreover, nobody's gonna understand how it works.

An annotation processor can probably do nothing for you. A Lombok-style-hacking processor can. You want to make

@MyAnnotation
private static final int MY_CONSTANT;

work like

private final static int MY_CONSTANT =
    Properties.getInteger(ThisClass.class, "MY_CONSTANT");

The original expression doesn't compile (due to the uninitialized final variable), but it parses fine and Lombok can do its job. There's already something related there:

So actually, you could write just

@MyAnnotation
int MY_CONSTANT;

and let your annotation change also the modifiers. I'd look at the eclipse and javac handlers for @UtilityClass, I guess all you need is to generate the initializer (which is quite some work because it's all damn complicated).

I don't think Lombok itself will implement this anytime soon, since

  • all the static stuff is non-testable and mostly bad style
  • and not everyone wants this in their code
  • it's not that much boilerplate
  • it also magically refers to the class Properties, but this could be solved via configuration

but I guess a contribution might be accepted.

Community
  • 1
  • 1
maaartinus
  • 44,714
  • 32
  • 161
  • 320
1

Actually not quite clear why and what do you want to archive.

As I correctly undestand, you want use special kind of annotations to automatically assign values for static final constants from some properties file. Unfortunatelly it is impossible without special hacks. And annotations have nothing to do with this.

The reason is that final fields must be initialized and it is compiler's request. There aren't special annotations in java which will provide such syntactic sugar which you want.

But if you insist on this there are two ways:

  1. Extrim way. Init all properties field with default value. Then using this hack in some static init section initialize this value using reflection mechanism and you code via reading values from properties.

  2. Less extrim way: refuse request of final modifiers for properties fields, and using only reflection fill these fields values.

And additionally, for these ways, yes you can use annotations. But you will have to solve following technical issues:

1) Find all fields in all classes in classpath, which are annotated with you special annotation. Look at: Get all of the Classes in the Classpath and Get list of fields with annotation, by using reflection

2) Force your Properties class to be initialized in all possible enter points of your application. In static section in this class you will load your properties file, and then using (1) method with reflection and classloader, assign values to all constants.

Community
  • 1
  • 1
Andremoniy
  • 34,031
  • 20
  • 135
  • 241