views:

943

answers:

3

Is there a well-established approach for documenting Java "properties" file contents, including:

  • specifying the data type/contents expected for a given key
  • specifying whether a key is required for the application to function
  • providing a description of the key's meaning

Currently, I maintain (by hand) a .properties file that is the default, and I write a prose description of the data type and description of each key in a comment before. This does not lead to a programmatically accessible properties file.

I guess what I'm looking for is a "getopt" equivalent for properties files...

[EDIT: Related]

+1  A: 

One easy way is to distribute your project with a sample properties file, e.g. my project has in svn a "build.properties.example",with properties commented as necessary. The locally correct properties don't go into svn.

Since you mention "getopt", though, I'm wondering if you're really thinking of cmd line arguments? If there's a "main" that needs specific properties, I usually put it the relevant instructions in a "useage" message that prints out if the arguments are incorrect or "-h".

Steve B.
Your first paragraph tells him to do what he says he's already doing. Your second paragraph is irrelevant to the question (he clearly states that he's talking about properties files).
Michael Myers
Well, actually It'd seemed that he's talking about reading in a default file, not a "dead" example. The question's also been edited a bit for clarity.
Steve B.
+1  A: 

I have never seen a standard way of doing it. What I would probably do is:

  • wrap or extend the java.util.Properties class
  • override (of extending) or provide a method (if wrapping) the store method (or storeToXML, etc) that writes out a comment for each line.
  • have the method that stores the properties have some sort of input file where you describe the properties of each one.

It doesn't get you anything over what you are doing by hand, except that you can manage the information in a different way that might be easier to deal with - for example you could have a program that spit out the comments to read in. It would potentially give you the programmatic access that you need, but it is a roll-your-own sort of thing.

Or it might just be too much work for too little to gain (which is why there isn't something obvious out there).

If you can specify the sort of comments you want to see I could take a stab at writing something if I get bored :-) (it is the sort of thing I like to do for fun, sick I know :-).

Ok... I got bored... here is something that is at least a start :-)

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;


public class PropertiesVerifier
{
    private final Map<String, PropertyInfo> optionalInfo;
    private final Map<String, PropertyInfo> requiredInfo;

    {
        optionalInfo = new HashMap<String, PropertyInfo>();
        requiredInfo = new HashMap<String, PropertyInfo>();
    }

    public PropertiesVerifier(final PropertyInfo[] infos)
    {
        for(final PropertyInfo info : infos)
        {
            final Map<String, PropertyInfo> infoMap;

            if(info.isRequired())
            {
                infoMap = requiredInfo;
            }
            else
            {
                infoMap = optionalInfo;
            }

            infoMap.put(info.getName(), info);
        }
    }

    public void verifyProperties(final Properties properties)
    {
        for(final Entry<Object, Object> property : properties.entrySet())      
        {
            final String key;
            final String value;

            key   = (String)property.getKey();
            value = (String)property.getValue();

            if(!(isValid(key, value)))
            {
                throw new IllegalArgumentException(value + " is not valid for: " + key);
            }
        }
    }

    public boolean isRequired(final String key)
    {
        return (requiredInfo.get(key) != null);
    }

    public boolean isOptional(final String key)
    {
        return (optionalInfo.get(key) != null);
    }

    public boolean isKnown(final String key)
    {
        return (isRequired(key) || isOptional(key));
    }

    public Class getType(final String key)
    {
        final PropertyInfo info;

        info = getPropertyInfoFor(key);

        return (info.getType());
    }

    public boolean isValid(final String key,
                           final String value)
    {
        final PropertyInfo info;

        info = getPropertyInfoFor(key);

        return (info.verify(value));
    }

    private PropertyInfo getPropertyInfoFor(final String key)
    {
        PropertyInfo info;

        info = requiredInfo.get(key);

        if(info == null)
        {
            info = optionalInfo.get(key);

            if(info == null)
            {
                // should be a better exception maybe... depends on how you 
                // want to deal with it
                throw new IllegalArgumentException(key + " 
                                                   is not a valid property name");
            }
        }

        return (info);
    }

    protected final static class PropertyInfo
    {
        private final String name;
        private final boolean required;
        private final Class clazz;
        private final Verifier verifier;

        protected PropertyInfo(final String   nm,
                               final boolean  mandatory,
                               final Class    c)
        {
            this(nm, mandatory, c, getDefaultVerifier(c));
        }

        protected PropertyInfo(final String   nm,
                               final boolean  mandatory,
                               final Class    c,
                               final Verifier v)
        {
            // check for null
            name     = nm;
            required = mandatory;
            clazz    = c;
            verifier = v;
        }

        @Override
        public int hashCode()
        {
            return (getName().hashCode());
        }

        @Override
        public boolean equals(final Object o)
        {
            final boolean retVal;

            if(o instanceof PropertyInfo)
            {
                final PropertyInfo other;

                other  = (PropertyInfo)o;
                retVal = getName().equals(other.getName());
            }
            else
            {
                retVal = false;
            }

            return (retVal);
        }

        public boolean verify(final String value)
        {
            return (verifier.verify(value));
        }

        public String getName()
        {
            return (name);
        }

        public boolean isRequired()
        {
            return (required);
        }

        public Class getType()
        {
            return (clazz);
        }
    }

    private static Verifier getDefaultVerifier(final Class clazz)
    {
        final Verifier verifier;

        if(clazz.equals(Boolean.class))
        {
            // shoudl use a singleton to save space...
            verifier = new BooleanVerifier();
        }
        else
        {
            throw new IllegalArgumentException("Unknown property type: " + 
                                               clazz.getCanonicalName());
        }

        return (verifier);
    }

    public static interface Verifier
    {
        boolean verify(final String value);
    }

    public static class BooleanVerifier
        implements Verifier
    {
        public boolean verify(final String value)
        {
            final boolean retVal;

            if(value.equalsIgnoreCase("true") ||
               value.equalsIgnoreCase("false"))
            {
                retVal = true;
            }
            else
            {
                retVal = false;
            }

            return (retVal);
        }
    }
}

And a simple test for it:

import java.util.Properties;


public class Main
{
    public static void main(String[] args)
    {
        final Properties         properties;
        final PropertiesVerifier verifier;

        properties = new Properties();
        properties.put("property.one",   "true");
        properties.put("property.two",   "false");
//        properties.put("property.three", "5");
        verifier = new PropertiesVerifier(
            new PropertiesVerifier.PropertyInfo[]
            {
                new PropertiesVerifier.PropertyInfo("property.one",   
                                                    true, 
                                                    Boolean.class),
                new PropertiesVerifier.PropertyInfo("property.two",   
                                                    false, 
                                                    Boolean.class),
//                new PropertiesVerifier.PropertyInfo("property.three", 
//                                                    true, 
//                                                    Boolean.class),
            });

        System.out.println(verifier.isKnown("property.one"));
        System.out.println(verifier.isKnown("property.two"));
        System.out.println(verifier.isKnown("property.three"));

        System.out.println(verifier.isRequired("property.one"));
        System.out.println(verifier.isRequired("property.two"));
        System.out.println(verifier.isRequired("property.three"));

        System.out.println(verifier.isOptional("property.one"));
        System.out.println(verifier.isOptional("property.two"));
        System.out.println(verifier.isOptional("property.three"));

        System.out.println(verifier.getType("property.one"));
        System.out.println(verifier.getType("property.two"));

        // System.out.println(verifier.getType("property.tthree"));
        System.out.println(verifier.isValid("property.one", "true"));
        System.out.println(verifier.isValid("property.two", "false"));
        // System.out.println(verifier.isValid("property.tthree", "5"));


        verifier.verifyProperties(properties);
    }
}
TofuBeer
@TofuBeer: Thanks... I am actively playing with something much like this for our project... JA
andersoj
Well, I've done extensive research and the code you've posted is the best thing out there... thanks! --JA
andersoj
+2  A: 

You could use some of the features in the Apache Commons Configuration package. It at least provides type access to your properties.

There are only conventions in the traditional java properties file. Some I've seen include providing, like you said, an example properties file. Another is to provide the default configuration with all the properties, but commented out.

If you really want to require something, maybe you're not looking for a properties file. You could use an XML configuration file and specify a schema with datatypes and requirements. You can use jaxb to compile the schema into java and read it i that way. With validation you can make sure the required properties are there.

The best you could hope for is when you execute your application, it reads, parses, and validates the properties in the file. If you absolutely had to stay properties based and didn't want to go xml, but needed this parsing. You could have a secondary properties file that listed each property that could be included, its type, and whether it was required. You'd then have to write a properties file validator that would take in a file to validate as well as a validation schema-like properties file. Something like

#list of required properties
required=prop1,prop2,prop3

#all properties and their types
prop1.type=Integer
prop2.type=String

I haven't looked through all of the Apache Configuration package, but they often have useful utilities like this. I wouldn't be surprised if you could find something in there that would simplify this.

John Ellinwood