Ant transforms using Groovy template scriptdef

April 9, 2012

Ant provides powerful transformation capability. For example, the copy task can include filters that replace tokens in the text. There is also the XSLT task to perform more complex XML based transforms.

Sometimes you may need more dynamic or procedural transforms for non-xml based templates. While this can be done in Ant, they should not be; Ant should really only be used in a declarative manner. One can do this outside of Ant using custom solutions or use the various templating frameworks, such as Velocity and Freemarker, which provide Ant plugins.

Another alternative is the Groovy language which has always provided text processing in the form of GStrings and templates. These are used in various solutions such as Groovlets and Groovy Server Pages. Below I show how Groovy templates can be easily used in Ant.

listing 1 shows a meeting agenda text template with a list of topics. Note that it has a GString interpolated string using “$” character. It also, analogous to Java Server Pages, uses a scriptlet with <% and %> to execute code. Yes I know, scriptlets are evil.

List 1, template1.txt, a template:

Hello ${name}!
Agenda is:
<% 
   subject.split(',').each{
%>- $it 
<% }	
%>

In listing 2 below, an Ant script invokes the “template” task to transform the template in listing 1.

Listing 2, build.xml, use template task:

<project default="init">
	
<import file="tasks.xml"/>
	
<target name="init" 
  xmlns:jb="http://josefbetancourt.wordpress.com">
  <jb:template 
	src="template1.txt" 
	dest="temp.txt" 
	property="result" 
	failOnError="true">
		
	<property name="name"    
		value="world"/>
	<property name="subject" 
		value="beans,GStrings,templates"/>
		
  </jb:template>
	
  <echo>${result}</echo>
	
</target>	
</project>

Listing 3 shows an example run.

Listing 3, example use:

init:
Saving result to temp.txt
     [echo] Hello world!
     [echo]     Agenda is:
     [echo]     - beans
     [echo]     - GStrings
     [echo]     - templates
     [echo]

BUILD SUCCESSFUL

Listing 4 is the implementation. It is a simple scriptdef using Groovy as the script language. The Groovy code is inline but could have been in external file using the “src” attribute of the scriptdef declaration.

Listing 4, tasks.xml, the scriptdef implementation:

<project>
<!-- config stuff not shown -->

<!--
Render a Groovy template using properties to create the binding 
 -->	
<scriptdef name="template" 
  language="Groovy" 
  classpathref="groovy.lib" 
  uri="http://josefbetancourt.wordpress.com">
	
  <attribute name="src"/>   <!--  required -->
  <attribute name="dest"/>  <!--  optional -->
  <attribute name="property"/> <!--  optional -->
  <attribute name="failOnError"/> <!--  optional, true -->
  <element name="property" type="property"/> <!--  required -->
	
<![CDATA[
def SRC= 'src';
def PROPERTY = 'property'
def DEST = 'dest'
def FAILONERROR = 'failonerror'
		
try{
	// convert element properties to map
	def map = [:]
	elements.get(PROPERTY).each{
		map.put(it.name,it.value)
	}
			
	// render the template
	String result = new groovy.text.GStringTemplateEngine()
	  .createTemplate(new File(attributes.get(SRC))
	  .getText())
	  .make(map).toString()
	
	def prop = attributes.get(PROPERTY)
	if(prop){ // save to property?
		project.setNewProperty(prop, result)
	}
		
	def dest = attributes.get(DEST)
	if(dest){ // write to file?
		project.log("Saving to $dest",project.MSG_INFO)
		new File(dest).write(result)
	}
}catch(Exception ex){
	def f = attributes.get(FAILONERROR)
	def failOnError = f ? f : true;

	if(failOnError == 'true'){
		throw ex;
	}		
}			
]]>
</scriptdef>  <!-- end name="template" -->

</project>

Summary
Shown was an example of using Groovy templates in an Ant task. A simple scriptdef was created to allow reuse in Ant scripts.

Extensions
Kind of begs the question, since scriptlets have a bad rep, should there instead be a way of using taglibs in the templates. I think Grails implemented Groovy taglibs.

References


Jenkins CI Server is great

March 16, 2012

Finally got a Jenkins server installed. Had a host of system issues, like communicating to our source code repo.

Jenkins is a joy to use. Well, it is not perfect, what is? Like, I need to pass the user’s name that invoked a build via Jenkins to the target DOS script (yea, Windows) that eventually invokes the legacy Ant scripts. A quick Google search shows that this is asked in various ways, but no answers. For example, here or here. Hmmmm.

Anyway, now comes a trial use, to see if it is what we really need and can we manage it to do what we will want. With 400 plugins, I don’t see how it could lack. Plus, I’m sure I can use the Groovy plugin to cobble something up. Jenkins even includes a Groovy Console. Finally, there is a road map for possible migration of legacy Ant scripts to Gradle using the Gradle Plugin.

I take back my past snarky comment. Jenkins is not just a pretty face on Cron.

BTW, is there some Wiki law that says a wiki shall never ever have a link to the parent project? If you get tossed into a wiki by following a link, invariably you will click in agony at links that should go to the real home. Instead, you have to edit the URL in the address bar. Since I never curse, I can’t write “wtf”.

Off Topic
Was watching the Easyb intro video. BDD is interesting. Definitely “should” is a better then “test”. With so many great tools why are products still bug ridden?

More stuff

  1. Jenkins home page
  2. Continuous integration Not a very good Wikipedia article
  3. Continuous Integration Much better
  4. Continuous Integration in Agile Software Development
  5. Hooking into the Jenkins(Hudson) API
  6. Five Cool Things You Can Do With Groovy Scripts
  7. Parameterized Builds in Jenkins – choosing subversion folders
  8. Groovy Console
  9. Groovy plugin
  10. Switching to Jenkins–Download and Install Artifact Script for Tester
  11. Gradle Plugin

Groovy script to bootstrap a Gradle Project

March 8, 2012

I took Ted Naleid’s “Quick Shell Function to Bootstrap a Gradle Groovy Project” example code and converted it to a Groovy script.

Listing 1.

// NewGradle.groovy
// Author: Josef Betancourt
// Based on T. Naleid's shell script 

println "Creating files for new Gradle project ..."

new File(".gitignore").withPrintWriter{ w ->
     "*.un~,*.iml,*.ipr,*.iws,build,.gradle".split(",").each{ 
         w.println(it)
     }
}

new File("build.gradle") << ="""\
apply plugin: 'groovy'
apply plugin: 'idea'
apply plugin: 'eclipse'
 
repositories {
    mavenCentral()
}
 
dependencies {
    groovy 'org.codehaus.groovy:groovy:1.8.6'
    compile 'org.apache.ivy:ivy:2.2.0'
}
 
task createSourceDirs(description : 
   'Create empty source directories for all defined sourceSets') 
   << {
        sourceSets*.allSource.srcDirs.flatten().each { 
            File sourceDirectory ->        
            if (!sourceDirectory.exists()) {
                println "Making \$sourceDirectory"
                sourceDirectory.mkdirs()
            }
        }
}
 
idea {
    project {
        jdkName = '1.6'
    }
}

""" // end content

"cmd /c gradle createSourceDirs".execute()

"cmd /c git init".execute()

Thread.start{
	sleep 5000 // allow time for all files to be created
	new File(".").eachFile{
		println it
	}
}

Not expert Groovy, but was easy to do. The bulk of it is the creation of a “here” doc using Groovy’s triple quote string. I didn’t duplicate the last line of Naleid’s script: “ls -a1 && find src # list all created assets”.

This script is not fully cross-platform. The invocation of shell commands at the end are in the Windows format. Left as an exercise to reader is the use of inline AntBuilder to reuse Ant’s exec task. :-)

Updates
2012-03-09: Tweaked the source. Removed use of two temp variables.
2012-03-09: Added the Eclipse plugin. Now after creating the Gradle project executing eclipse will create the eclipse project: gradle eclipse
Or instead generate the Idea project: gradle idea.

Further Reading

  1. Copy of this post
  2. Groovy (Programming language)
  3. Groovy
  4. Gradle
  5. Quick Shell Function to Bootstrap a Gradle Groovy Project
  6. Strings and GString
  7. Groovy JDK extensions
  8. Executing External Processes
  9. Using Gradle to Bootstrap your Legacy Ant Builds

Off Topic


How to fail an Ant Groovy Scriptdef task

February 18, 2012

While you can certainly just throw an exception in a scriptdef to stop an Ant script that is using a scriptdef, perhaps it is best to use Ant’s Exit task. This allows Ant to do whatever it has to do as it handles the resulting BuildException.

The exit task is used in an Ant script as “fail”. To use ‘fail’ in a scriptdef you just use the predefined ‘self’ variable that references the scriptdef instance. This is illustrated in listing 1 below.

An alternative to the scriptdef ant task is to use the ‘groovy’ task that comes with the Groovy environment. This is perhaps the simplest approach when you don’t need the full structure that the scriptdef task makes available to a script. The scriptdef task allows one to pass in Ant XML elements and attributes.

Listing 1

<project name="example" default="demo">

  <!-- reference the groovy libs -->
  <path id= "libs" >
    <fileset dir= "../lib">
      <include name="groovy/groovy-all-1.8.6.jar" />
    </fileset>
  </path>

  <!-- An inline scriptdef that throws an exception -->
  <scriptdef name="solveProblem" 
        language="Groovy" classpathref="libs">

     self.fail("intentionally failed")	 

  </scriptdef>

  <!-- a call of the scriptdef -->
  <target name="demo">
	<solveProblem/>
  </target>
</project>

A run …

>ant -f fail.xml
Buildfile: fail.xml

demo:

BUILD FAILED
fail.xml:15: intentionally failed

Total time: 1 second

Further reading


Using Commons SCXML with Groovy

October 16, 2011

The Watch example is rewritten using Groovy. The actual state machine document is created using Groovy‘s MarkupBuilder.

More then a decade ago I used a state machine approach to develop a web application framework. It worked, which surprised me most of all, lol! Since then I’ve wondered why there is no generic reuseable facility to use this approach.

SCXML is “…a general-purpose event-based state machine language that combines concepts from CCXML and Harel State Tables”. It would seem to fit the bill.

Many applications would probably benefit from having the control flow managed by some kind of flow engine. Sometimes we wind up with the internal object’s implicit FSM mixed into the application FSM in tangles of confusion and brittleness.

Modern software engineering is just the use of more ingenious complex frameworks for obfuscating the finite state machines really being created. This is especially true in the Java world, where there is a tendency toward frameitis.

Listing one below is a version of the Watch example presented on the Commons SCXML site. Instead of subclassing the AbstractStateMachine, this version uses the SCXMLExecutor and extends SCXMLListener. The listener allows the engine to invoke methods on the demo class by setting itself as the invoker: executor.registerInvokerClass(“watch”,getClass());

Note: The Groovy Grab will not work in Eclipse. I just put the required jars in the build path. The grab will work in the command line.

Listing one

// file: ScxmlDemo.groovy
import groovy.xml.*
import groovy.grape.*

import java.awt.datatransfer.Transferable;
import java.lang.reflect.Method
import org.apache.commons.scxml.*
import org.apache.commons.scxml.io.*
import org.apache.commons.scxml.env.*
import org.apache.commons.scxml.invoke.*
import org.apache.commons.scxml.model.*
import org.apache.commons.scxml.env.jsp.*

@Grapes(
	[@Grab('commons-scxml:commons-scxml:0.9'),
	@Grab('commons-el:commons-el:1.0'),
	@Grab('commons-logging:commons-logging:1.1.1')]
)

/**
 * 
 * Demo of 
 * "...State Chart XML (SCXML), which is a general-purpose 
 * event-based state machine language that combines 
 * concepts from CCXML and Harel State Tables."
 * 
 * Invocation:
 *   groovy src\ScxmlDemo.groovy
 * @author jbetancourt
 * 
 */
public class ScxmlDemo implements SCXMLListener {
	SCXMLExecutor executor
	def namespace = "http://www.w3.org/2005/07/scxml"
	Map methods = [:]
	
	enum Trans{
		TSTART("watch.start"),TSPLIT("watch.split"),
		TSTOP("watch.stop"),TUNSPLIT("watch.unsplit"),
		TRESET("watch.reset");
				
		String name;
		public Trans(String s){
			name = s
		}
	}

	/**   */
	public ScxmlDemo(){
		["doReset","doRunning","doPaused","doStopped"].
		each{
			methods.put(it,
				this.getClass().
				getDeclaredMethod(it, new Class[0]))
		}
	}

	/** Instead of writing XML, we use a DSL builder to create it */
	def createScxmlDoc(){
		def writer = new StringWriter()
		def builder = new MarkupBuilder(writer)

		builder.scxml(
		  xmlns:namespace,version:"1.0",initialstate:"reset"){
		     state(id:"reset"){
			   transition(
				event:Trans.TSTART.name,target:"running"){
			   }
		     }
		     state(id:"running"){
			   transition(
				event:Trans.TSPLIT.name,target:"paused"){
		           }
		           transition(
			        event:Trans.TSTOP.name,target:"stopped"){
			   }
		     }
		     state(id:"paused"){
			    transition(
			        event:Trans.TUNSPLIT.name,target:"running"){
			    }
			    transition(
				event:Trans.TSTOP.name,target:"stopped"){
			    }
				}
		     state(id:"stopped"){
			   transition(
				event:Trans.TRESET.name,target:"reset"){
			   }
		     }
		}

		new File("sm.xml").setText(writer.toString())
		writer.close()
	}

	/**   */
	public void execute(){
		createScxmlDoc()
		def eh = new
			org.apache.commons.scxml.env.SimpleErrorHandler()
		SCXML model = SCXMLParser.parse(
				new File("sm.xml").toURI().toURL(), eh)

		executor = new SCXMLExecutor()
		executor.setRootContext(new SimpleContext())
		executor.setEvaluator(new ELEvaluator())
		executor.setErrorReporter(new SimpleErrorReporter())
		executor.setStateMachine(model)
		executor.setEventdispatcher(new SimpleDispatcher())
		executor.addListener(model, this)
		executor.registerInvokerClass("watch",getClass());

		executor.go()

		fireEvent(Trans.TSTART.name);
		def status = executor.getCurrentStatus().
				getAllStates()
		status.each{ println "Now state is: ${it.id}" }
	}

	/**  */
	public void fireEvent(name){
		def evts = [
			new TriggerEvent(name,
			TriggerEvent.SIGNAL_EVENT, null)
		];

		executor.triggerEvents(evts as TriggerEvent[])
	}

	/**  */
	def invoke(id){
		try {
			def name = 'do' + id.substring(0,1).
				toUpperCase() + id.substring(1)
			def method = methods[name]
			method.invoke(this, new Object[0]);
			return true
		}catch(Exception ex){
			ex.printStackTrace()
		}
		
		return false
	}

	/**  */
	@Override
	public void onEntry(
	final TransitionTarget entered) {
		invoke(entered.getId());
	}

	/**
	 *
	 * @param from The &quot;source&quot; transition target.
	 * @param to The &quot;destination&quot; transition target.
	 * @param transition The transition being followed.
	 */
	@Override
	public void onTransition(
	   final TransitionTarget from,
	   final TransitionTarget to,
	   final Transition transition) {
		// nothing to do
	}

	/**
	 *
	 * @param exited The transition target being exited.
	 */
	@Override
	public void onExit(final TransitionTarget exited) {
		// nothing to do
	}

	// the activities
	def show(s){println "I'm '$s' ..."}
	public void doReset() {show('reset')}
	public void doRunning() {show('running')}
	public void doPaused() {show('paused')}
	public void doStopped() {show('stopped')}

	public static main(args){
		def main = new ScxmlDemo()
		main.execute()
	}
}

Example run:

groovy src\ScxmlDemo.groovy
I'm 'reset' ...
I'm 'running' ...
Now state is: running

Summary
Presented was an example of implementing the Watch example application using the Groovy programming library. The Groovy MarkupBuilder gives a “better” language then XML for creating the state machine document.

SCXML can be the basis of a State-Oriented Programming approach.

Updates

Further Reading

 


My Funny Valentine / Bill Evans & Jim Hall


Current Git repo branch name using Groovy

October 14, 2011

As some light research I was looking at how to determine a Git repository’s current branch name in a Groovy script.

In the Git CLI this would be shown with:

C:\temp\gitSdWf\project>git branch
  d12345-validation-fails
  d54321-login-box
  f52123-error-report
* master

The current branch is shown with a leading asterisk.

Invoke Git
One method is to just invoke the Git executable:

gitExe = "\\servers\\Git\\bin\\git.exe"
currentBranch = ''

matcher = ~/\* (.*)\s/

process = "${gitExe} branch".execute()
process.in.eachLine{ 
     line -> println line
     m = line =~ /\*\s+(.*)\s?/
     if(m){ 	
          currentBranch = m[0][1] 
          return
     }
}

println "current: '$currentBranch'"

Use JGit library
Another is to use a Java based Git library like JGit:

@GrabResolver(
     name='jgit-repository', 
     root='http://download.eclipse.org/jgit/maven'
)
@Grab(
     group='org.eclipse.jgit',
     module='org.eclipse.jgit',
     version='1.1.0.201109151100-r'
)

import org.eclipse.jgit.*
import org.eclipse.jgit.lib.*
import org.eclipse.jgit.util.*

directory = new File(args[0])

Repository repository =
  RepositoryCache.open(
       RepositoryCache.FileKey.lenient(directory,FS.DETECTED), 
       true)
	
println(repository.getFullBranch())	

This would be run as:
groovy testGit.groovy \temp\gitSdWf\depot\project.git
refs/heads/master

One problem with the JGit approach is that, afaik, this won’t work if the repo is represented by a ‘pointer’ file, i.e., created with the –separate-git-dir option. Also, unlike the Git branch command, you must open the target git repo at it’s root level, not a nested subdirectory of the repo.

Environment
— Windows 7 64bit Professional
— git version 1.7.6.msysgit.0
— Groovy Version: 1.8.2 JVM: 1.6.0_25

References


Oregon – Yet To Be (live)


Archive files using Ant from Groovy

September 12, 2011

In Ant The zip task takes a fileset but only creates one archive. How do you zip files into individual archives?

Use case
One use case for this is log files. Sooner rather then later, every system must deal with the growth of log files. One of the best utilities for handling this is logrotate. This is available on *nix systems. There are probably native versions that do similar tasks in Windows, but I did not locate any ‘free as in beer’ ones.

Approach
Since I’m working with Ant, on Windoze, and can’t install cygwin or other system, will use Groovy to get around Ant limitations. This will not be an attempt to recreate logrotate, of course.

As in a previous post, we can use the Groovy AntBuilder and iterate a fileset to repeatably call an Ant task. In this case we’ll be invoking the zip task. Note that the same approach could be used to create a scriptdef to do this directly in an Ant build script. This would be more usable since we could declaratively define the fileset, etc.

Example

First the configuration. In listing 1, we use a JSON configuration file. No reason, I’m just tired of Property files.

Listing 1, JSON configuration file

{ "about":
    {
    	"project":"archivelogs",
    	"author":"jbetancourt",
    	"date":"20110909T2156"    	
    },
  "config":
	{
		"basedir":"test/logs",
		"archivedir":"test/logs/archive",
		"includes":"*.log",
		"propertyName":"found.files",
		"when":"before",
		"update":"true",
		"days":"1",
		"level":"9"
	}
}

In listing 2, the main entry point calls the config loader and then the ‘archiveLogs’ method.

The fileset created with the AntBuilder uses an Ant ‘date’ selector to find the files that are x amount of days old. Then the resulting fileset is scanned in a loop, invoking the zip task as we go and then deleting the just zipped file. It is assumed here that the target file name has descriptive information, like a date. And, we are not adding a “rotate” counter to the name for future logrotate like behavior.

Listing 2, Groovy archive source

package ant

import org.apache.tools.ant.*
import java.text.*
import groovy.json.JsonSlurper

/**
 * @author jbetancourt
 */
class ArchiveLogs {
	def format = "yyMMdd'T'kkmm"
	def df = new SimpleDateFormat(format)

	/** entry point */
	static main(args) {
		def als = new ArchiveLogs()	
		
		def p = (
		  (args) ?
		  args[0] : "test/conf/archivelogs.json")
				
		def map = als.loadConfiguration(p)
		als.archiveLogs(map)
	}

	/**  */
	def archiveLogs(conf) {
		def base = conf.base
		def targetDateString = df.format(
			(new Date() - Integer.parseInt(conf.days))
		)
		
		def ant = new AntBuilder()		
		ant.fileset( 
			dir:conf.basedir, 
			includes:conf.includes, 
			id:conf.propertyName){			
				date( 
					datetime:targetDateString,
					when:conf.when,
					pattern:format)
		}	
		
		def ref = ant.project.getReference(
			 conf.propertyName)
		
		ref.each{ fileResource ->
			File file = fileResource.getFile()
			def name = file.getName()
			
			ant.zip(
				basedir:conf.basedir,
				destfile:
				 "${conf.archivedir}/${name}.zip",
				update:conf.update,
				level:conf.level,
				includes:name
			)
			
			ant.delete(verbose:"true",file:file.getPath())			
		}		
		
	}	
	
	/** load json file */
	def loadConfiguration(String path){
		def slurper = new JsonSlurper()
		def obj = slurper.parse(
			 new File(path).newReader())
		
		if(obj.config.debug == 'true'){
			obj.config.each{
				println it
			}		
		}
		
		return obj.config
		
	}
}

Caveats
This is, of course, a simplistic approach. For example, what if the file is being used when we attempt to delete it? Or we fail to zip the file, but continue to delete it? And, this source, being an example, does not have any error handling.

Alternatives
The ant-contribs project has looping support, foreach. Directly using the Java util packages for archiving, such as java.util.zip. Or, the libraries found in, for example, the Apache commons project.

Further Reading


“cafe”, egberto gismonti


Groovy Scriptdef to detect properties with trailing spaces

September 2, 2011

One potential source of errors in a project is the presence of undesired terminating spaces on properties loaded by java.util.Properties. Since property files are also used to deploy or configure applications, it is possible for these sources of errors to disrupt deployed services.

Alternatives
There are of course many options to prevent this from happening, such as:
1. Property file or general editors that can detect or show “spaces”.
2. Another is to create property file loaders that can ‘trim’ spaces.
3. And, yet another is that configuration properties are part of the resources that a management layer monitors as part of an autonomic processing.
4. Perhaps an optimal and most immediate approach is to just have actual tests triggered on any changes, and, where it matters, properties are validated according to specified rules.

The simple approach
An Ant Scriptdef definition implemented in the Groovy language. This can be easily used in the build and deployment process. It can also be used at the deployment location or remotely to check deployed property files.

In listing 1, an Ant script shows the definition of the scriptdef and its use.

Listing 1, Snippet of Ant script

<scriptdef name="blankpropertycheck" 
      language="Groovy" 
      classpathref="libs" 
      src="TrailingSpacePropertiesCheck.groovy">		
		
             <attribute name="failonerror"/>
             <attribute name="property"/>
             <attribute name="verbose"/>
		
             <element name="fileset" type="fileset"/>		
</scriptdef>

<target name="init"  description="---initialize build">
    <!-- use the scriptdef -->	
    <blankpropertycheck
           failonerror="true"
           property="anyFound">		

        <fileset dir=".">
             <include name="**/*.properties"/>
        </fileset>		
			
    </blankpropertycheck>
		
    <echo>Properties with trailing spaces:</echo>
    <echo>${anyFound}</echo>
		
</target>

The new task definition specification:

Attributes
failonerror optional
verbose optional, one of verbose or property
property optional, one of verbose or property
Elements
fileset required

Now we create two example property files in the directory (not shown here) and run the Ant script, with results shown in listing 2. This output was produced with a debug variable set true, to show the data binding in the scriptdef.

Listing 2, sample run

C:\temp\ant\blanksCheck>ant
Buildfile: C:\temp\ant\blanksCheck\build.xml

init:
[blankpropertycheck] ****************************************************************
[blankpropertycheck] failOnError='true'
[blankpropertycheck] propToSave='anyFound'
[blankpropertycheck] verbose='null'
[blankpropertycheck] fileset='abc.properties;lib\log4j.properties;lib\xyz.properties'
[blankpropertycheck] ****************************************************************
     [echo] Properties with trailing spaces:
     [echo] C:\temp\ant\blanksCheck\abc.properties
     [echo]     four=[4    ]
     [echo]     twoWithSpaces=[2  ]
     [echo] C:\temp\ant\blanksCheck\lib\xyz.properties
     [echo]     badwiththreespace=[m   ]

BUILD SUCCESSFUL
Total time: 1 second

The Groovy script is shown in listing 3 below. As implemented, the failonerror is for script or variable binding errors. Since properties could by design have trailing spaces, this is not considered an error. It is easy to change the script to behave differently, of course.

The scripts also serves as an example (to myself) of:

  • regular expression use
  • dynamic object to boolean conditionals
  • accessing object properties via a map
  • reusing closures as actions

Listing 3

/*
    File: TrailingSpacePropertiesCheck.groovy
    Author: josef betancourt
*/

import org.apache.tools.ant.*
import org.apache.tools.ant.types.*

boolean debug = true

failOnError = attributes.get("failonerror")
propToSave = attributes.get("property")
verbose = attributes.get('verbose')
fileset = elements.get('fileset')[0] as FileSet

if(debug){
    band = '*' * 72
    println(band)
    ['failOnError','propToSave','verbose','fileset'].each{
        println "$it='${this[it]}'"
    }
    println(band)
}

// Do we have all essential attributes and elements?
def msg =''
msg =  !fileset ? "'fileset' not found;" : ''
msg += !(verbose || propToSave) ?
         "both 'verbose' and 'propToSave' not found" : '';

if(msg){
    if(failOnError){
        throw new BuildException(msg)
    }
    
    println msg
    return    
}


pattern =  ~/(\s+)$/

/** Count any trailing spaces in string  */
int countTrailingSpaces(aString){
    def matcher = pattern.matcher(aString)
    def result = matcher.find()
    
    if(result){
        def blanks = matcher[0][1]
        return blanks.size()
    }
    
    return 0
}


def props = new Properties()
def count
// map of  file:[list of potential properties in error]
def results = [:]

// analyize each file in fileset
fileset.each{
    File file = it.getFile()    
    props.load(file.newReader());
    def list = []
    
    props.each{ entry ->
        count = countTrailingSpaces(entry.value)
        if(count){
            list.push(entry)
        }
    }
    
    if(list){
        results[file.getPath()] = list
    }
    
    props.clear()

} // end each file

def action // a closure
def resString = ""

if(!results){
    return
}

if(verbose){
    action = { x -> println x}
}else{
    // converts to StringBuilder
    resString <<= ""
    action = {x -> resString << (x + "\n") }
}
    
results.each{
    action("${it.key}")
    it.value.each{
        action("\t${it.key}=[${it.value}]")
    }
}
    
if(!verbose){
    project.setNewProperty(propToSave,resString.toString())
}

// end of file TrailingBlankPropertiesCheck.groovy

Properties
Should any properties in configuration or data files have trailing spaces? Seems like having one of those “kick me” signs on your back. If one could use visible terminators like a quote or brace, but usually a Java based properties file will not have terminators, afaik.

Summary
Shown was a simple method of checking property files for entries with trailing spaces on values.

Further Reading

  • The Groovy Truth” Groovy has special rules for coercing non-boolean objects to a boolean value. Using this language feature makes coding much more productive.

Extend a Groovy Scriptdef with inline task

August 23, 2011

If you use AntBuilder within a Scriptdef (which does sound weird), how do you then use inline task elements?

Intro
In a prior post, “Groovy AntBuilder in Ant Scriptdef to Replace Props,” I used an AntBuilder to iteratively create an Ant PropertyFile task. The task was then used as shown in listing 1.

Listing 1 – Snippet of prior Ant script

  <!-- use the "propertiesreplace" scriptdef -->
  <target name="merge" depends="init">

       <propertiesreplace 
              targetfile="oldProps.properties" 
              newpropfile="newProps.properties"
       />

  </target>

Using elements
But now I want (don’t ask, I really did have a good use case) to also explicitly define other replacements using the PropertyTask directly. Sure I could use it after the use of the scriptdef, but how would it be used inside the scriptdef itself? So now the use case is:

Listing 2 – New Ant script with PropertyFile task element

  <!-- use the "propertiesreplace" scriptdef -->
  <target name="merge" depends="init">

       <propertiesreplace 
              targetfile="oldProps.properties" 
              newpropfile="newProps.properties"

              <propertyfile file="oldProps.properties">
                <entry key="ten" value="XX"/>			
              </propertyfile>
       />

  </target>

We still want to use the scriptdef defined task since we are merging property files, but we also want to make other replacements (or modify those that the files would create).

Solution
You’d think, at least I did for a while, that you have to delve into the arcane internals of Groovy’s NodeBuilder and all that. You don’t. See listing 3; you just execute it.

Listing 3 – New Scriptdef handling element

<!-- Replace properties in oldfile 
whose key match those in newfile -->

<scriptdef name="propertiesreplace" language="groovy"
    classpathref="libs">

    <attribute name="mergefile"/>
    <attribute name="propfile"/>

    <![CDATA[
     def ant = new AntBuilder(project)

     ant.propertyfile( file:attributes.get("mergefile") ){
          def props= new Properties()
          props.load(
               new File( attributes.get("propfile") )
                     .newReader()
          )

          props.each{ cur ->
               entry( key:cur.key,value:cur.value )
          }
     }

     def pf = elements.get("propertyfile").get(0)
     pf.execute()
    ]]>
</scriptdef>

Update
Feb 18, 2012: Wow, I looked at listing 3 and couldn’t remember how that worked.
“elements” is a map of the xml nodes within the scriptdef invocation shown in listing 2. Since it is a map we get the corresponding element by key, get(key). Why the final get(0)? Hmmmm. Oh yeah, there may be more then one element with the same name, so we just get the first in this present example.

Off topic
1. It would be nice to have a new GDK extension on Properties class, so that we can load a properties file and iterate as we do with Map. For example:

             new Properties(filepath).each{
                     println "${it.key}=${it.value}"
             }

2. AntBuilder makes using Ant effortless. Imagine copying a log4j properties file to target folder, editing an entry, then printing out the file. Sure you can you use the copy task with nested tokenizer, but not always. Here it is with PropertyFile:

def ant = new AntBuilder()

ant.tstamp(){
  format(property:"BUILD.DATE",pattern:"yyyy-MM-dd'T'HHmmss")
}

def now = ant.project.getProperty("BUILD.DATE")
def path = "../../../user/temp/${args[0]}"

ant.copy(
  file:"../lib/log4j.properties",
  todir:path
)

ant.propertyfile(file:"${path}/log4j.properties"){
    entry(
      key:"log4j.appender.LogFile.file",
      value:"builder-${now}.log"
    )
}

print (new File(targetFile).getText())

That is pretty nice. Not my code, the concept.

3. Along these lines some projects provide even better use of Groovy to handle ‘build’ stuff. The Gant project puts a lightweight facade upon the AntBuilder to create a tool for Ant scripting. And, Gradle takes that to a whole new level to create a “build framework”.

Further Reading

  1. Using Ant from Groovy
  2. Gradle
  3. Gant
  4. Writing Domain-Specific Languages
  5. Groovy for Domain-Specific Languages

Proxy A Groovy Ant BuildListener?

August 20, 2011

In my last post BuildListener using Groovy In Ant Scriptdef, I got a scripted BuildListener to work. Was wondering if there were a way to use a Proxy to stand in for the listener.

See update below for another attempt that succeeds.

In short, I can’t. The first problem was classloader issues, but I think I fixed that by clearing the CLASSPATH:
set CLASSPATH =

Then a host of issues, until finally I give up. I think the big complexity is that BuildListener is an Interface. That means you have to jump hoops to proxy it. I’m sure Groovy can do this, but I don’t see it yet. May need more info on Groovy’s MOP.

Perhaps there is too much indirection being used. Is modern software engineering just ingenious ways to hide a GOTO? Just kidding.

Using commons-proxy library
Apache’s commons-proxy puts a more usable framework around different proxy factories.

Listing 1 – Proxy using Javassist library

import org.apache.commons.proxy.factory.javassist.*
import java.lang.reflect.*
import org.apache.commons.proxy.*
import org.apache.tools.ant.*

class MyListener implements Invoker  {
    static int count = 0;

    Object invoke(Object proxy, Method method, Object[] arguments) 
      throws Throwable{
	new File("finished.run").setText("count: ${count++}")   
    }

}

MyListener proxy = new MyListener()		
java.lang.ClassLoader loader = proxy.class.getClassLoader()

JavassistProxyFactory factory = new JavassistProxyFactory()

Object listener = factory.createInvokerProxy(  
	loader,
	proxy,
	[BuildListener.class] as Class[]
);

project.addBuildListener(listener)		

// end source

Listing 2 – build script

<project name="listener" default="default" basedir=".">
<!--
File: build.xml, author: J.Betancourt,
    date:2011-08-18T2137
-->

<path id="libs">
    <fileset dir="lib">
	<include name="commons-proxy-1.0.jar" />
	<include name="javassist.jar"/>
	<include name="groovy-all-1.8.0.jar"/>
    </fileset>
</path>

<!-- Groovy library -->
<typedef resource="org/codehaus/groovy/antlib.xml" classpathref="libs" />    

<!-- sets a BuildListener to the project -->
<scriptdef name="set-listener" language="Groovy" 
   classpathref="libs" src="lib/BuildListener.groovy">

  <attribute name="endTarget"/>

</scriptdef>

<!-- install the listener -->
<set-listener  endTarget="list"/>    

<target name="default">
    <fail message="doh!"/>    
</target>

</project>
    
   

Listing 3 – stacktrace snippet

C:\temp\ant\groovyListener>ant
Buildfile: C:\temp\ant\groovyListener\build.xml

BUILD FAILED
java.lang.ClassCircularityError: groovy/runtime/metaclass/java/io/FileMetaClass
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.messageLogged(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireMessageLoggedEvent(Project.java:2254)
        at org.apache.tools.ant.Project.fireMessageLogged(Project.java:2290)


Update

This works, but just as wordy as the original using a BuildListener class implementation.

import org.apache.tools.ant.*

listenerMap = [
  theTarget : attributes.get("endtarget"),
  buildFinished: { event ->	
  new File("finished.run").
    setText(
     "Build Finished:  message='${event.message}' exception='${event.exception}'"
    )
	
    project.executeTarget(listenerMap.theTarget)
	
  },

  buildStarted : {},
  messageLogged : {},
  targetFinished : {},
  targetStarted : {},
  taskFinished : {},
  taskStarted : {},
]

listener = listenerMap as BuildListener

project.addBuildListener(listener )		

Uses the technique outlined at:
Groovy Way to implement interfaces

Conclusion

Updates

Further Reading

Listing 4 – Full stacktrace

C:\temp\ant\groovyListener>ant
Buildfile: C:\temp\ant\groovyListener\build.xml

BUILD FAILED
java.lang.ClassCircularityError: groovy/runtime/metaclass/java/io/FileMetaClass
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.messageLogged(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireMessageLoggedEvent(Project.java:2254)
        at org.apache.tools.ant.Project.fireMessageLogged(Project.java:2290)
        at org.apache.tools.ant.Project.log(Project.java:470)
        at org.apache.tools.ant.Project.log(Project.java:459)
        at org.apache.tools.ant.AntClassLoader.log(AntClassLoader.java:396)
        at org.apache.tools.ant.AntClassLoader.findClass(AntClassLoader.java:1310)
        at org.apache.tools.ant.AntClassLoader.loadClass(AntClassLoader.java:1064)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.taskFinished(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireTaskFinished(Project.java:2206)
        at org.apache.tools.ant.Task.perform(Task.java:364)
        at org.apache.tools.ant.Target.execute(Target.java:390)
        at org.apache.tools.ant.helper.ProjectHelper2.parse(ProjectHelper2.java:180)
        at org.apache.tools.ant.ProjectHelper.configureProject(ProjectHelper.java:82)
        at org.apache.tools.ant.Main.runBuild(Main.java:793)
        at org.apache.tools.ant.Main.startAnt(Main.java:217)
        at org.apache.tools.ant.launch.Launcher.run(Launcher.java:280)
        at org.apache.tools.ant.launch.Launcher.main(Launcher.java:109)

Total time: 1 second
java.lang.ClassCircularityError: groovy/runtime/metaclass/java/io/FileMetaClass
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.messageLogged(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireMessageLoggedEvent(Project.java:2254)
        at org.apache.tools.ant.Project.fireMessageLogged(Project.java:2290)
        at org.apache.tools.ant.Project.log(Project.java:470)
        at org.apache.tools.ant.Project.log(Project.java:459)
        at org.apache.tools.ant.AntClassLoader.log(AntClassLoader.java:396)
        at org.apache.tools.ant.AntClassLoader.findClass(AntClassLoader.java:1310)
        at org.apache.tools.ant.AntClassLoader.loadClass(AntClassLoader.java:1064)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:169)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:127)
        at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
        at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:242)
        at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:71)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:190)
        at MyListener.invoke(Script1.groovy:10)
        at JavassistUtilsGenerated_0.taskFinished(JavassistUtilsGenerated_0.java)
        at org.apache.tools.ant.Project.fireTaskFinished(Project.java:2206)
        at org.apache.tools.ant.Task.perform(Task.java:364)
        at org.apache.tools.ant.Target.execute(Target.java:390)
        at org.apache.tools.ant.helper.ProjectHelper2.parse(ProjectHelper2.java:180)
        at org.apache.tools.ant.ProjectHelper.configureProject(ProjectHelper.java:82)
        at org.apache.tools.ant.Main.runBuild(Main.java:793)
        at org.apache.tools.ant.Main.startAnt(Main.java:217)
        at org.apache.tools.ant.launch.Launcher.run(Launcher.java:280)
        at org.apache.tools.ant.launch.Launcher.main(Launcher.java:109)
groovy/runtime/metaclass/java/io/FileMetaClass

 

 

 


 

“Timeless” solo 12 string guitar played by Ralph Towner on his “Solo Concert” CD. YouTube


Follow

Get every new post delivered to your Inbox.