views:

238

answers:

2

The original title was "How to generate enum from properties file using ant?"

I want to iterate over all properties and generate enum class that have every property.

Im thinking about writing custom task, but I think i would need to put it in extra jar :|

im using maven and i want to do it in generate-sources phase.

A: 

I would strongly recommend you to reconsider.

You risk ending up hard coding against values which are coming from configuration files and might change any moment.

I think a little wrapper class around a HashMap or BidiMap reading the properties file will achieve the almost the same benefits and the developers later on will not pull out their hairs why they get a gazillion compilation errors because of a small change in a property file.

I have done my share of code generation. They're great for parsers and protocol handlers but are ticking timebombs for about every other use case I had the misfortune to use them for.

Peter Tillemans
i used it for jQuery selectors for tests. Why you think thats a bad idea? if someone removes selector than he should also remove/update test that is using that. i just exchanged runtime errors to compilation errors. plus this way i can easly escape : in selectors(it only works as \\:).
01
I think you had a problem with that because u did not kept the generated classes under source-control, so when something got broken it was really hard to fix it.
01
As long as the generated code is self contained it is not a problem, the problems start if you start coding in classes which are not generated against functions with generated signatures. And no, the generated classes were not under source control, only the files from which they were generated, and generation was done by the build system. It might very well work if your code is really well tested, and everybody on the development and maintenance team knows it. Actually it looks like a good use case : generating "stupid" reptitive code which has a limited use case.
Peter Tillemans
I've used this solution recently in a java/flex project where we needed common error codes, it was very helpful to make sure we were 'speaking the same language' across platforms (obviously the flex code was also generated)
seanizer
This makes sense... Thanks, I think I need to rethink my position on code generation. We are indeed facing different challenges in a polyglot world than when I formed this opinion.
Peter Tillemans
+3  A: 

Although I somewhat agree with Peter Tilemans, I was also tempted by this problem and I hacked up a solution using groovy and the GMaven-Plugin. EDIT: The great thing about GMaven is that you can access the maven object model directly without creating a plugin first and still have groovy's full programming power.

What I do in cases like this is to create a source folder called src/main/groovy that is not part of the actual build process (and hence will not contribute to the jar / war etc). There I can put groovy source files, thus allowing eclipse to use them as groovy source folders for autocompletion etc without changing the build.

So in this folder I have three files: EnumGenerator.groovy, enumTemplate.txt and enum.properties (I did this for simplicity's sake, you will probably get the properties file from somewhere else)

Here they are:

EnumGenerator.groovy

import java.util.Arrays;
import java.util.HashMap;
import java.util.TreeMap;
import java.io.File;
import java.util.Properties;

class EnumGenerator{

    public EnumGenerator(
        File targetDir, 
        File propfile,
        File templateFile,
        String pkgName,
        String clsName 
    ) {
        def properties = new Properties();
        properties.load(propfile.newInputStream());
        def bodyText = generateBody( new TreeMap( properties) );
        def enumCode = templateFile.getText();
        def templateMap = [ body:bodyText, packageName:pkgName, className: clsName  ];
        templateMap.each{ key, value -> 
                                enumCode = enumCode.replace( "\${$key}", value ) } 
        writeToFile( enumCode, targetDir, pkgName, clsName )
    }

    void writeToFile( code, dir, pkg, cls ) {
        def parentDir = new File( dir, pkg.replace('.','/') )
        parentDir.mkdirs();
        def enumFile = new File ( parentDir, cls + '.java' )
        enumFile.write(code)
        System.out.println( "Wrote file $enumFile successfully" )
    }

    String generateBody( values ) {

        // create constructor call PROPERTY_KEY("value")
        // from property.key=value
        def body = "";
        values.eachWithIndex{
            key, value, index ->
                body += 
                ( 
                    (index > 0 ? ",\n\t" : "\t")
                    + toConstantCase(key) + '("' + value + '")'
                )   
        }
        body += ";";
        return body;

    }

    String toConstantCase( value ) {
        // split camelcase and dot.notation to CAMEL_CASE and DOT_NOTATION
        return Arrays.asList( 
            value.split( "(?:(?=\\p{Upper})|\\.)" ) 
        ).join('_').toUpperCase();
    }

}

enumTemplate.txt

package ${packageName};

public enum ${className} {

${body}

    private ${className}(String value){
        this.value = value;
    }

    private String value;

    public String getValue(){
        return this.value;
    }

}

enum.properties

simple=value
not.so.simple=secondvalue
propertyWithCamelCase=thirdvalue

Here's the pom configuration:

<plugin>
    <groupId>org.codehaus.groovy.maven</groupId>
    <artifactId>gmaven-plugin</artifactId>
    <version>1.0</version>
    <executions>
        <execution>
            <id>create-enum</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>execute</goal>
            </goals>
            <configuration>
                <scriptpath>
                    <element>${pom.basedir}/src/main/groovy</element>
                </scriptpath>
                <source>
                    import java.io.File
                    import EnumGenerator

                    File groovyDir = new File( pom.basedir,
                      "src/main/groovy")
                    new EnumGenerator(
                        new File( pom.build.directory,
                          "generated-sources/enums"),
                        new File( groovyDir,
                          "enum.properties"),
                        new File( groovyDir,
                          "enumTemplate.txt"),
                        "com.mycompany.enums",
                        "ServiceProperty" 
                    );

                </source>
            </configuration>
        </execution>
    </executions>
</plugin>

And here's the result:

package com.mycompany.enums;

public enum ServiceProperty {

    NOT_SO_SIMPLE("secondvalue"),
    PROPERTY_WITH_CAMEL_CASE("thirdvalue"),
    SIMPLE("value");

    private ServiceProperty(String value){
        this.value = value;
    }

    private String value;

    public String getValue(){
        return this.value;
    }

}

using the template, you can customize the enum to suit your needs. and since gmaven embeds groovy in maven, you don't have to install anything or change your build configuration.

The only thing to remember is that you'll need to use the buildhelper plugin to add the generated source folder to the build.

seanizer
cool idea, thanks.
01
+1 for creativity (again)
Pascal Thivent
@pascal (we'll be seeing some more of that, I hope :-))
seanizer