The Source for Java Technology Collaboration
User: Password:



Joerg Plewe's Blog

November 2004 Archives


Functional Ant?

Posted by herkules on November 22, 2004 at 01:29 AM | Permalink | Comments (2)

Guessing what an Ant project does

Typical Ant projects are designed to deliver a single jar as a result.

E.g. look at the projects produced by NetBeans4 or the NetBeans project itself. One module - one build.xml - one jar.
Additionally, when depending on such a project, it is normally necessary to know how the jar is named and where it is stored to. Thus, depending on an Ant project is using a side effect of the build function.
How the jar is called and where it is stored - somehow that feels like an implementation detail of the Ant script. What, if the build layout changes? The jar gets a version number? The Ant script decides to break things up into 2 or more jars?

"Ok, this never happens, or I'll create another project" you might say. But the single-jar-result scheme also prohibits from subproject aggregation and resolving transitive dependencies.

When my project 'myproject' depends on 'lib_a' which in turn depends on 'lib_b', the common ant usage forces to guess

  • what lib_a's jar might be called and where it can be found
  • that lib_b depends on lib_a
  • what lib_b's jar might be called and where it can be found
  • what lib_b depends on....
Lots of guessing. In practice, the guessing is avoided by a set of conventions. Like there always has to be a directory 'build' or 'dist', where the jar has to be stored, schemes on how it has to be named and so on and so on.

A more functional solution

Maven addresses these kind of issues in a very convenient and stylish way. But it also requires to use that very complex system and induces a certain project structure, which basically again means to manifest a set of assumptions (see above).

For my pet project DRTS (Distributed RealTime System) I have chosen a solution that makes Ant build a bit more functional. The Ant call for 'build the subproject' can sort of 'give back' the build result to the caller like a function call.

For that, I'm putting the actual build.xml aside and let it do what it wants to do. An additional file export.xml is created that serves as the projects interface to the world. When depending on the project, only export.xml will be talked to (by <import>ing it).

export.xml typically is quite small and does not contain many targets. And theses targets are named project specific:

<target name="headquarter_core.build" depends="-headquarter_core.init,hcutil.build">
....

From a depending project, this export.xml is <import>ed and thus gives access to the specific target that resolve the dependency. Here is an example usage:


<project name="flyingguns_core_client" basedir="." default="all">

  <!-- import standard targets, also defining where to find the export.xml files -->
  <import file="../../../../etc/stdtargets.xml"/>
 
  <!-- read dependant modules -->
  <import file="${modules.hcutil.export}"/>
  <import file="${modules.threed.java3d.core.export}"/>
  <import file="${modules.headquarter.core.export}"/>
 
  <target name="required_modules" 
    depends="init,hcutil.build,threed_java3d_core.build,headquarter_core.build" 
    description="build required subprojects">
    <!--
      Set the important classpath variable reqmodules.classpath.
      This was the main goal of this target!
    -->	
    <path id="reqmodules.classpath">
      <fileset refid="hcutil.jar.fileset"/>   
      <fileset refid="threed_java3d_core.jar.fileset"/>   
      <fileset refid="headquarter_core.jar.fileset"/>   
      <fileset refid="fg.additional.libs.fileset"/>   
    </path>
  </target>
 
  ...
<project/>

export.xml is informed about all internals of the build.xml it covers. Where it will produce the results and how they are called. It exposes this information in a unique way, like this:


<target name="headquarter_core.build" depends="-headquarter_core.init,hcutil.build">
  		
  <!-- do the ant call -->
  <ant dir="${headquarter_core.basedir}/etc" antfile="build.xml" 
    target="build" inheritAll="false"/>
  
  <!-- create the result filesets -->
  <fileset id="headquarter_core.jar.fileset" file="${headquarter_core.jar.file}"/>
  
</target>

headquarter_core.jar.fileset is the canonical name of result, the return value of the target headquarter_core.build. In this sense, the target can be used in a functional manner.

In this example, again only one jar is produced and the transitive dependency to hcutil also is not resolved. Unfortunately, filesets always need a common basedir, so headquarter_core.jar.fileset cannot be extended e.g. to hold also the hcutil.jar.fileset. But we can accomplish that at least for the classpath as demonstrated in the next example: the definition of the third-party libraries, also dealing with platform specific things.


<?xml version="1.0" encoding="UTF-8"?>
<project name="third_party.export">

  <!-- use ANT mechanics to find out where I am -->
  <dirname property = "third_party.export.path" file = "${ant.file.third_party.export}"/>
  <property name = "third_party.basedir" location = "${third_party.export.path}/.."/>
		
  <!-- this is the collection of all jars -->
  <target name="-third_party.list_of_libs">

    <!--	junit -->
    <property name = "third_party.junit.lib.version" value="3.8.1"/>
    <fileset id = "third_party.junit.lib.fileset"           
        file="${third_party.basedir}/junit/lib/junit.jar"/>
    <!--	vecmath, quite popular -->
    <property name = "third_party.vecmath.lib.version" value="1.3.2"/>
    <fileset  id   = "third_party.vecmath.lib.fileset"      
        file="${third_party.basedir}/java3d/win32/lib/ext/vecmath.jar"/>
    <!-- Java3D, win32 -->
    <property name = "third_party.java3d.win32.lib.version" 
        value="132-pre8-0410011108"/>
    <fileset  id   = "third_party.java3d.win32.lib.fileset" 
        dir="${third_party.basedir}/java3d/win32/lib/ext"/>
    <property name = "third_party.java3d.win32.binpath"     
        location="${third_party.basedir}/java3d/win32/bin"/>
    <!--	Java3D, linux -->
    <property name = "third_party.java3d.linux.lib.version" 
        value="132-pre8-0410011108"/>
    <fileset  id   = "third_party.java3d.linux.lib.fileset" 
        dir="${third_party.basedir}/java3d/linux/lib/ext"/>
    <property name = "third_party.java3d.linux.binpath"     
        location="${third_party.basedir}/java3d/linux/lib/i386"/>
    <!--	Java3D, sparc -->
    ....
    <!--	StarFire research 3DS loader 	-->
    <property name = "third_party.starfire.lib.version"     
        value="2.20"/>
    <fileset  id   = "third_party.starfire.lib.fileset"     
        file="${third_party.basedir}/starfire/StarfireExt.jar"/>
    <!--	JXInput -->
    <property name = "third_party.jxinput.lib.version"      
        value="0.3.3"/>
    <fileset  id   = "third_party.jxinput.lib.fileset"      
        file="${third_party.basedir}/jxinput/jxinput.jar"/>
    <property name = "third_party.jxinput.binpath"
        location="${third_party.basedir}/jxinput"/>
			
  </target>
	
  <!-- init -->
  <target name ="third_party.init" 
       depends ="std.determine_platform,-third_party.list_of_libs,-third_party.setupPlatforms">
				
    <!-- from the filesets, create the modules classpath to be referenced -->
    <path id="third_party.classpath">
      <fileset	refid = "third_party.junit.lib.fileset"/>
      <fileset	refid = "third_party.vecmath.lib.fileset"/>
      <fileset	refid = "third_party.java3d.lib.fileset"/>
      <fileset	refid = "third_party.starfire.lib.fileset"/>
      <fileset	refid = "third_party.jxinput.lib.fileset"/>
    </path>
  </target>
  
  <!-- platform specific setup for windows -->
  <target name="-third_party.setupWindows" if="std.isWindowsOnX86">
    <fileset  id="third_party.java3d.lib.fileset"  
        dir="${third_party.basedir}/java3d/win32/lib/ext"/>
    <!-- pointer to native libs -->
    <property name="third_party.java3d.binpath"    
        location="${third_party.basedir}/java3d/win32/bin"/>
    <!-- the collected native path -->
    <property name="third_party.binpath"           
        value="${third_party.java3d.binpath};${third_party.jxinput.binpath}"/>
  </target>
  
  <!--	platform specific setup for linux -->
  <target name="-third_party.setupLinux" if="std.isLinuxOnX86">
    ....
  </target>
  		
  <!--	platform specific setup for solaris -->
  <target name="-third_party.setupSparc" if="std.isSolarisOnSparc">
    ....
  </target>

</project>

How to find a export.xml?

In DRTS, the scheme of <import>ing export.xml is further supported by a central set of scripts, namely modules.xml and stdtargets.xml. By <import>ing stdtargets.xml, modules.xml is automatically evaluated. modules.xml contains the overview of all modules, their places in the directory and espacially the pointers to the modules export.xml files. So it somehow compares to a Maven repository.

Other advantages

The system desribed uses Ant filesets, classpath and the depends clause for information transport. In contrast to properties, missing or unspecified elements in any of these cause Ant to fail. Thus, besides having a functional aspect, it also adds some security provided by Ant itself.

This obviously is another example of µ-architecture....

µ-architecture - what's that?

Posted by herkules on November 01, 2004 at 09:15 AM | Permalink | Comments (2)

When talking about architecture, people think in terms like 1,2,3,4...n-tier, J2EE, client/server, thin vs. thick client. The pieces put together are Oracle vs. DB2, BEA vs. JBoss, Struts, Swing, JSP, JavaFaces, etc.. This is the big picture! It is obvious that the decisions taken there are extremely important and can loose the battle before it begins. But can they also win the battle?

From my observation, there is a second level of architecture, not less important, which I will call micro-architecture now.
Even in a good, well chosen highlevel architecture, the code written in the teams can be a real mess! Unreadable, unmaintainable, far too many lines of code, inconsistant, many bugs. This happens because not every single team member is a universal genius and too often they have to deal with things they are not-so-good at or are just lacking experience. There are many APIs around to deal with and most of them are complex.

Have you ever seen what happens when a former VisualBasic or MFC coder constructs a Swing GUI? A massacre!!

Maybe GUI is a good example here, because it can be done right and it can do done so miserable. Highlevel architecture defines: we do it in Swing! Period. Well done. Good decision! Now the heterogenous team just starts extending a JFrame, adding JTextFields, JButtons and so on. Half a year later, your whole business logic is distributed over a net of FocusListeners, KeyListeners, AncestorListeners. Zillions of lines of code, an ugly, inconsistant GUI and the whole team totally occupied with just GUI coding and bug fixing.

As you might guess, the µ-architect had been missing! Or he was there, but nobody noticed.

Consider there had been one single experienced Swing guru somewhere in your team. Unrecognized so far. Coming along with a nice little API that separates data from presentation and introduces abstract events (like 'user wants to open something' in favor of 'mousePressed', 'keyPressed', 'focusLost'). He shows a clean path how to apply a simple MVC pattern (I have something in mind like JDNC or JGoodies, just maybe even more focused).
Then this guy is your µ-architect. Listen to him!

GUI is only one example. There are more. A cool way to do logging? Access the database? I18N? They all have in common that they focus on a small aspect of the development process. Nevertheless, the impact can be huge!

Most of the time, µ-architectures are just called APIs. But they are more. Typically, they are coupled with a certain way of coding and/or a development process. If they are good, they can enforce a slim process and high quality of code measured in terms of consistency or few lines of code. They can avoid the necessity to generate huge amounts of code or enforce clean SoC.

µ-architects typically don't have an accentuated position that is known to be architectural in any way. They can be just one member of a coding team. Sometimes, they don't even know - how important they are and what they do. They are code-level architects, artists in some respect, black-belted, experienced code wizards with a good feeling for quality and style. They love to edit well-formatted code, care for the IDE they use, don't like meetings so much.

Do you know who you µ-architects are? Are you sure they are paid good enough? Take care!





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds