开发者

Dynamically populate String's from a Properties file in Java

Does anyone know of a method to load a properties file, and dynamically create Strings with identical names to the key value?

I'm trying to clean up my code by moving all the system messages etc out of the logic and into a properties file, but want to avoid having to have a class consisting of dozens of lines like the following:

final String COMMS_ERROR = properties.getProperty(COMMS_ERROR);

An example of what I'm trying to achieve:

for (String key : properties.getPropertyValues()) {
    final String <key> = properties.getProperty(key)
}

Obviously this won't work, the compiler will throw a fit. But I'm wondering if there's an elegant solution to do the same thing - create new Strings using the key names from the properties file - be it via a separate library or in my own code.

One solution I've thought of is to populate a HashMap with the keys/values from the properties file开发者_JAVA百科, but then that would mean less elegant code in the form of:

import com.x.y.messages;
...
    throw new Exception(HM.get("COMMS_ERROR"));

Where HM is the HashMap located within com.x.y.messages...

Ideally I just want to be able to do:

import com.x.y.messages;
....
    throw new Exception(COMMS_ERROR);

Any thoughts/advice appreciated.


If those properties can change after compilation (if not, then why would they be used) you'd not have any chance to create AND use those strings dynamically. Sure, there are ways to dynamically create code (like AOP runtime weaving) but that code would not be usable in the normal compilation process.

So how would the compiler know that COMMS_ERROR actually exists in this line throw new Exception(COMMS_ERROR);? It can't and thus you'd need to go for the HashMap approach. Note that Properties is actually a Map<String, String> (ok, it is a Hashtable<Object, Object> as of Java 6 but it acts like a Map<String, String>), thus there's no need to create a new one.

Edit: what you could do is use static imports like this:

package yourpackage;

public class Props
{
  private static Properties props;

  public static String prop(String prop)
  {
    return props.getProperty( prop );
  }
}

Use it like this:

import static yourpackage.Props.prop;

....

prop("someKey");

Note that static import has its drawbacks like looking as if the methods were part of the class it uses, so I'd just like to provide an alternative and let you decide whether to use it or not.


What is wrong with

        Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources( "com/x/y/message.properties" );

        while( resources.hasMoreElements() ) {
            URL url = resources.nextElement();
            Properties p = new Properties();
            p.load( url.openStream() );
            ...
        }

i dont see why store data from Properties to HashMap

import com.x.y.messages;
    ....
throw new Exception(p.getProperty("COMMS_ERROR"));


You cannot declare local variables on the fly but you can use a map:

Map<String, String> messages = new HashMap<String, String>();

for (String key : properties.getPropertyValues()) {
    messages.put(key, properties.getProperty(key));
}

to use them:

throw new Exception( messages.get( "KEY" ) )

See http://download.oracle.com/javase/6/docs/api/java/util/Map.html

But in fact as Thomas pointed out above you don't need a new HashMap just

throw new Exception( properties.getProperties(key) );


I have previously written helper classes myself that kept a Properties file in sync with a Constants class. But that only works if you stick to conventions.

Lets say you have a class like this:

public final class Constants{

    private Constants(){}
    public static final String SOME_PROPERTY_NAME = "some.property.name";
    public static final String THIS_ONE_NOT_SET_YET = null;
    public static final String PROPERTY_NOT_DEFINED = "property.not.defined";

}

and a property file like this:

some.property.name=Hello World
no.constant.for.this.yet=Hello again

What my helper class would do was to loop over all properties and all constants, make matches and identify those that didn't correspond to anything.

So in this case:

a)

In Constants.java,

public static final String THIS_ONE_NOT_SET_YET = null;

would be changed to

public static final String THIS_ONE_NOT_SET_YET = "this.one.not.set.yet";

and in the properties file, this line would be introduced:

this.one.not.set.yet=

b)

in the properties file, this line would be added

property.not.defined=

c)

In Constants.java, this line would be added:

public static final String NO_CONSTANT_FOR_THIS_YET = "no.constant.for.this.yet";

It's not perfect, but that way you get pseudo-compile-time safety. You compile against constants, and your helper keeps those constants in sync with he properties.

Obviously this approach gets a lot more complicated if you have more advanced scenarios. E.g.

  • Properties starting with "foo." being stored in "foo.properties" while properties named "bar." are being stored in "bar.properties"
  • Internationalization: Now you have foo.properties, foo.properties.es, foo.properties.de etc. Keeping that in sync is a major nuissance.

Perhaps one thing to consider would be to have your constants class dynamically created from one or more properties files during the build process. Your code generator (a Main class, a Groovy script or even a shell script) would basically just have to do this (pseudocode):

properties = readProperties()
writeClassHeader()
for prop : properties
    writeln "public static final String " 
            + prop.name.upperCase().replace(".","_") + "= \"" + prop.name + "\";"
writeClassFooter()


I'm not aware of a tool that would do this, and it doesn't fit the normal Java way of doing things. (In Java you can't add new variables on the fly ... unlike Javascript for example.)

It is theoretically possible to implement something along these lines, but it would probably entail generating and compiling a class for each kind of property file, and recompiling the rest of your code against these classes APIs. Unless you've got huge numbers of these property files, it is easier to code the classes by hand. (And if you do have huge numbers of these properties files, I would be inclined to see if there was a better way to handle the information in those files.)


Yeah that's what I was hoping for - a library that would contain the necessary magic

Unfortunately no ordinary library could do this. The generation / recompilation has to happen at build time. (A library could generate the classes at runtime and even compile and load them. But getting it to recompile the rest of your application at runtime is at best difficult, and typically impossible ... because the source code is not available.)


It looks almost exactly what my library does! Check it out: http://owner.aeonbits.org

Example:

# Properties file (call it the same name as the Java class and put 
# it in the same package
port=80
hostname=foobar.com
maxThreads=100

//properties mapper interface
public interface ServerConfig extends Config {
    int port();
    String hostname();
    int maxThreads();
}

// how you use it:

ServerConfig cfg = ConfigFactory.create(ServerConfig.class);
System.out.println("Server " + cfg.hostname() + ":" + cfg.port() + " will run " + cfg.maxThreads());

But, you can do much more with OWNER library.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜