Tips & Tricks

This page lists a couple tips about Java.
Usually when a feature is counter-intuitive or annoying I will try to post it here.

Opening resources that are outside the application jar

It is pretty common to have to open InputStream from files that are outside your jar application.
Like for example configuration files: many people end up putting those files within the jar because it’s easier, but in the end it becomes difficult to edit your configuration files, which defeats the purpose of having them!

Lets say in the folder you have:
app.jar
lib/utils.jar
config/conf.xml

The way to load conf.xml in your application app.jar is:

  • From your code, use “myclass.class.getResourceAsStream(”/conf.xml”);”. The initial “/” is important, otherwise it is treated as a “relative resource” and some package string is prepended (sun doc is not clear on this).
  • You need to tweak the manifest in the jar so that the classpath includes the config folder. The attribute should be “Class-Path: lib/utils.jar config/”. The last “/” is important, otherwise it doesnt work. Super confusing since “.” works fine.

That’s it.
Note that the folder in the classpath is considered to be relative to the folder in which the app.jar file is located. Hence if you execute the jar from a random folder, the classpath is not broken (that’s fortunate).

Basic string manipulation within Ant

Ant is a nightmare. Period.
It handles complex cases properly, if you are lucky and your need fits exactly what was intended. But for all simple cases & operations, it makes it impossible or super convoluted.
Here is the deal: in your property files you have a “run classpath”, that includes your classes directory, library jars and maybe a configuration folder. It all runs fine.
Now you want to package your application as a jar, like 99% of the people. So ant should build a jar that has a correct classpath to use all that.
Let’s say your run.classpath is “build/classes:lib/utils.jar:config/”.
You want the jar.classpath to be “lib/utils.jar config/”. Couple of things to note:

  • the separator is a space ” ” instead of a “:”
  • you don’t want the classes directory, since it will be the root inside the jar and thus is implicit.
  • the config folder has to end with “/”, otherwise it does not let you reference files in it. See earlier section about this.

So you think there is an easy way to get jar.classpath from run.classpath… there is not.
You can try to use the “pathconvert” utility with a pathsep of ” “, but forget it:

  • It will convert each path to an absolute path :’(
  • You can use a “flattenmapper” to circumvent this, which is complex and then you only end up with the basename of the jar!
  • You can then add another mapper to change each path to be within another folder, like “lib”. This forces you to use one single folder for all kind of resources you need.
  • Even then… it will strip off the last “/” of the config folder, which breaks the path.
	<pathconvert property="jar.classpath" pathsep=" ">
	    <path path="${run.classpath}"/>
	    <mapper>
		<chainedmapper>
		    <flattenmapper/>
		    <globmapper from="*" to="lib/*"/>
		</chainedmapper>
	    </mapper>
	</pathconvert>

The good way to do this is to just use some string manipulation on the path, which should be the first feature of ant!
Fortunately a side project ant-contrib adds the functionnality to do this through propertyregex.
First, add the jar in the “lib” folder of your project.
Then, in your build.xml file, include the new task:

	<taskdef resource="net/sf/antcontrib/antcontrib.properties">
	    <classpath>
		<pathelement location="lib/ant-contrib-1.0b3.jar"/>
	    </classpath>
	</taskdef>

Then you can easily modify the classpath to suit your need:

	
	<propertyregex property="jar.classpath.tmp"
		input="${run.classpath}"
		regexp=":"
		replace=" "
		casesensitive="false" />
	
	<propertyregex property="jar.classpath"
		input="${jar.classpath.tmp}"
		regexp="${build.classes.dir}"
		replace=""
		casesensitive="false" />

And then include the classpath in the jar:

	<jar manifest="${manifest.file}">
	    <fileset dir="${build.classes.dir}"/>
	    <fileset dir="${src.resources.dir}"/>
	    <manifest>
		<attribute name="Main-Class" value="${main.class}"/>
		<attribute name="Class-Path" value="${jar.classpath}"/>
	    </manifest>
	</jar>

Comments are closed.

Trackback this Post |