31 October 2006

JAVA custom Annotation & Concurrent Versions System

I manage my sources using CVS (Concurrent Versions System). When a user reported a bug in one of my java program, I faced the problem to get the version of the java class where the Exception was throwed..... I don't know if this was described but here is my solution using a custom java annotation. CVS can use a mechanism known as keyword substitution (or keyword expansion) to help identifying the files. Embedded strings of the form $keyword$ and $keyword:…$ in a file are replaced with strings of the form $keyword:value$ whenever you obtain a new revision of the file. Here I used Date,Source and Revision.

First I defined a new custom Annotation called @RCS returning the author, a date and a revision.

import java.lang.annotation.*;
/**
* describe a Revision Control System
* @author pierre
*
*/
@Retention(RetentionPolicy.RUNTIME) /* The annotation should be available for reflection at runtime.*/
@Target(ElementType.TYPE) /* place in : class, interface, enum */
public @interface RCS {
/** author of this source */
String author() default '[undefined]';
/** revision number of this source */
String revision() default '[undefined]';
/** date of revision of this source */
String date() default '[undefined]';
/** file name for this source */
String source() default '[undefined]';
}


and here are two files used for test. Both classes contains a @RCS annotation which can be find at runtime. Test2 jsut throws an Exception. Test1 catch this exception and display the stack trace. for each StackTraceElement it tries to find a @RCS annotation.


import java.lang.annotation.*;
@RCS( author='$Author: $',
date='$Date: $',
source='$Source: $',
revision='$Revision: $'
)

public class Test
{

Test()
{
Test2 t=new Test2();
t.doSomething();
}



public static void main(String args[])
{
try
{
Test t= new Test();
}
catch(Exception err)
{

System.err.println(err.getLocalizedMessage());
for(StackTraceElement e: err.getStackTrace())
{
System.err.println('Class :\t\t\t'+e. getClassName());
System.err.println('File :\t\t\t'+e. getFileName());
System.err.println('Line :\t\t\t'+e. getLineNumber());

if(e.getClassName()!=null)
{
try {
Class c= Class.forName( e.getClassName());
RCS rcsInfo=(RCS)c.getAnnotation(RCS.class);
System.err.println('Revision:\t\t'+(rcsInfo==null?'N/A':rcsInfo.revision()));
System.err.println('Date:\t\t\t'+(rcsInfo==null?'N/A':rcsInfo.date()));
System.err.println('Author:\t\t\t'+(rcsInfo==null?'N/A':rcsInfo.author()));
}
catch (Exception err2)
{
}
}
System.err.println();
}
}

}
}



@RCS(   author='$Author:  $',
date='$Date: $',
source='$Source: $',
revision='$Revision: $'
)

public class Test2
{
Test2()
{
}
void doSomething()
{
throw new RuntimeException('Test');
}

}


when the files where committed the CVS fields were substituted by their correct values. Here is the stack trace as it was printed:


pierre@linux:~/tmp/src> cvs commit
(...)
pierre@linux:~/tmp/src> javac Test.java
pierre@linux:~/tmp/src> java Test
Test
Class : Test2
File : Test2.java
Line : 13
Revision: $Revision: 1.2 $
Date: $Date: 2006/10/31 18:10:44 $
Author: $Author: pierre $


Class : Test
File : Test.java
Line : 15
Revision: $Revision: 1.5 $
Date: $Date: 2006/10/31 18:54:08 $
Author: $Author: pierre $


Class : Test
File : Test.java
Line : 24
Revision: $Revision: 1.5 $
Date: $Date: 2006/10/31 18:54:08 $
Author: $Author: pierre $



That's it.
Pierre

1 comment:

le C c'est nul said...

coucou,

fort bien mais ...
1. +tot que d'ecrire une gestion perso de tes exceptions, je dirais que pour ca tu aurais pu carrement reecrire le throwable et afficher automagiquement tes infos d'exception pour toute classe sans le gerer a chaque fois.

http://kickjava.com/src/java/lang/Throwable.java.htm

2. d'autre part la gestion du versionnement est necessaire pour une autre raison : la serialisation !
donc autant utiliser
l'interface qui sert a ca et
la variable serialVersionUID
ya meme un binaire "serialver"
qui sert a recuperer des infos sur ta classes

3. si tu utilises une signature pour tes classes (un applet signe ou webstart), dans le manifest de ton archive tu as deja un id unique qui est un hash du binaire. donc en demandant au classloader tu devrais pouvoir l'avoir aussi.

mais bon sinon l'id du CVS c'est pratique aussi.

@+, paul