Mon, Feb 7, 2005
Il faut croire que les billets s’attirent entre eux :-).
Au détour d’une recherche sur Java, voilà-t-y po que je tombe sur une
référence
à mon navigateur
préféré.
PS pour ceux qui ne suivent pas : / protected avec firefox. :D
Sat, Jan 22, 2005
Je viens à l’instant de découvrir enfin les joies de
l’ASM (j’ai bien dit ASM ! :-p). Je me
suis basé pour ce faire sur un exemple d’un de mes profs cette année,
Rémi Forax pour l’ASM et sur ce
billet
pour mieux comprendre le principe de l’agent java (cf. plus loin). Comme
c’est encore frais, j’en profite pour en faire un petit article qui sera
profitable, je pense, à d’autres et à moi pour plus tard ^^.
Cet outil permet en Java de rajouter du code à une classe pendant
l’exécution. C’est très impressionnant. Imaginons qu’on veuille faire
quelque chose que le compilateur ne permet pas. Je veux par exemple
renvoyer une classe d’un type différent, mais la même cependant, selon
des paramètres donnés, je n’ai pas d’autres choix que d’écrire les deux
classes différentes.
Pas avec ASM, justement.
Je vais ici expliquer avec un exemple le plus simple possible comment
rajouter une interface à une classe pendant l’exécution. Pour faire
cela, nous aurons besoin de trois choses (en plus de javac et java,
évidemment) :
Pensez aussi à vos petits doigts, ça peut servir.
Nous allons écrire une classe AddInterfaceClassTransformer qui hérite
de ClassFileTransformer. C’est cette classe qui va s’occuper de
modifier le bytecode de la classe qu’on veut changer au moment de son
chargement.
- On utilise un pattern Visitor pour en quelque sorte tager les
classes qu’on veut modifier ;
- On parcourt la classe pour réécrire le code qu’on souhaite en
fonction des tags par exemple.
En fait, on va indiquer à la VM qu’elle doit utiliser un “java agent”
avant de charger les classes. C’est ce principe qui va nous permettre de
modifier le code si on le souhaite.
La classe de test
On écrit cette toute petite classe qui “implémente” par exemple
EventListener, on lui fait implémenter quelque chose pour bien mettre
ensuite en évidence qu’il y en a bien une de plus :
public class MyClassOfMe implements EventListener
{
}
Le code de test
La classe suivante va nous permettre d’afficher par
reflection java
les interfaces de MyClassOfMe :
public class MonTest
{
public static void main(String[] args)
{
System.out.println("Liste des interfaces de "+MyClassOfMe.class+" : ");
for (Class interfaze : MyClassOfMe.class.getInterfaces())
{
System.out.println(interfaze);
}
System.out.println("Fin");
}
}
Si on lance cette classe après compilation, on obtient le résultat
suivant :
$ java fr/umlv/bmathus/MonTest
Liste des interfaces de class fr.umlv.bmathus.MyClassOfMe :
interface java.util.EventListener
Fin
Rien d’extraordinaire, on a affiché là ce qui est actuellement compilé
et qu’on a sous les yeux. Mais encore fallait-il vérifier que ça
fonctionne :-).
Maintenant, nous voudrions donc comme je l’ai dit que cette classe
affiche en fait une ligne de plus indiquant qu’elle implémente une
interface supplémentaire. J’ai choisi d’ajouter Serializable.
Écrivons donc le ClassFileTransformer qui va nous permettre de faire
ça :
public class AddInterfaceClassTransformer implements ClassFileTransformer
{
public byte[] transform(ClassLoader loader, String name, Class clazz,
ProtectionDomain domain, byte[] code)
throws IllegalClassFormatException
{
// if it's not the first load or a bootstrap class
if (loader == null || clazz != null)
return null;
System.out.println("[Loading " + name + "]");
ClassReader reader = new ClassReader(code);
// On "tag" le code à modifier
final MyVisitor myVisitor = new MyVisitor();
// On fournit le visitor au reader, on ne s'occupe pas des méthodes de
// debug (skipDebug=true)
reader.accept(myVisitor, true);
ClassWriter writer = new ClassWriter(false);
MyRewriteClassAdapter rewriteClassAdapter = new MyRewriteClassAdapter(
writer, myVisitor.mustAddInterface);
reader.accept(rewriteClassAdapter, true);
byte[] remoteCode = writer.toByteArray();
return remoteCode;
}
Ici, on définit en tant que classe interne le visitor qui va s’occuper
de marquer les classes qu’on souhaite modifier.
private class MyVisitor implements ClassVisitor
{
public MyVisitor()
{
}
public void visit(int version, int access, String name,
String superName, String[] interfaces, String sourceFile)
{
System.out.println("Nom de la classe analysée = " + name);
Je n’arrive pas utiliser getInternalName(MyClassOfMe.class) et que ça
fonctionne encore ici, donc je mets la chaîne en dur. Si quelqu’un a une
explication, je suis preneur.
if ("fr/umlv/bmathus/MyClassOfMe".equals(name))
{
mustAddInterface = true;
}
}
public void visitInnerClass(String _arg0, String _arg1, String _arg2,
int _arg3)
{
}
public void visitField(int _arg0, String _arg1, String _arg2,
Object _arg3, Attribute _arg4)
{
}
public CodeVisitor visitMethod(int _arg0, String _arg1, String _arg2,
String[] _arg3, Attribute _arg4)
{
return null;
}
public void visitAttribute(Attribute _arg0)
{
}
public void visitEnd()
{
}
boolean mustAddInterface;
}
private static class MyRewriteClassAdapter extends ClassAdapter
{
private boolean mustAddInterface;
public MyRewriteClassAdapter(ClassWriter writer,
boolean mustAddInterface)
{
super(writer);
this.mustAddInterface = mustAddInterface;
}
Ici, assez simplement, on récupère la liste des interfaces déjà
présentes. Et on y ajoute Serializable, l’utilisation d’un Set nous
permet de ne pas nous occuper de savoir si l’implémentation est déjà
présente. En effet, pour ceux qui l’ignorent, un Set est un ensemble.
Or, au sens mathématique, ça veut dire qu’on ne peut pas avoir deux fois
le même objet. Donc, ici, l’ajout ne se fera que si l’interface n’est
pas encore présente.
public void visit(int version, int access, String name,
String superName, String[] interfaces, String sourceFile)
{
HashSet<String> set;
if (interfaces == null)
set = new HashSet<String>();
else
set = new HashSet<String>(Arrays.asList(interfaces));
if (mustAddInterface)
{
set.add(I_SERIALIZABLE);
}
super.visit(version, access, name, superName, set
.toArray(new String[set.size()]), sourceFile);
}
private static String I_SERIALIZABLE = getInternalName(Serializable.class);
}
Là, nous donnons le point d’entrée du java agent. De la même façon que
la fonction main est le point d’entrée d’une classe normale, la méthode
premain est celui recherché par la vm lorsqu’on lui indique qu’une
classe est un agent java.
On lui passe ici une instance de notre transformer.
public static void premain(String agentArgs, Instrumentation inst)
{
inst.addTransformer(new AddInterfaceClassTransformer());
}
private static String getInternalName(Class< ? > clazz)
{
return clazz.getName().replace('.', '/');
}
private static Method defineClass;
static
{
try
{
defineClass = ClassLoader.class.getDeclaredMethod("defineClass",
String.class, byte[].class, int.class, int.class,
ProtectionDomain.class);
}
catch (NoSuchMethodException e)
{
throw new AssertionError(e);
}
defineClass.setAccessible(true);
}
private static HashSet<String> objectMethods = new HashSet<String>();
static
{
objectMethods.add("equals" + "(Ljava/lang/Object;)Z");
objectMethods.add("toString" + "()Ljava/lang/String;");
objectMethods.add("hashCode" + "()I");
}
}
Création du java agent
Comme je l’ai indiqué au début, cette classe doit être utilisée par la
vm avant de charger normalement les classes. Nous allons donc packager
notre classe dans un jar pour l’utiliser ensuite en tant qu’agent java.
Le manifest
Le manifest doit contenir une ligne indiquant la Premain-Class à
utiliser, il aura donc la forme suivante :
Premain-Class: fr.umlv.bmathus.AddInterfaceClassTransformer
À présent, nous pouvons créer notre jar :
$ jar cvfm transformer.jar META-INF/MANIFEST.MF fr
C’est presque fini, il ne reste plus qu’à lancer la même classe qu’au
début en précisant à la vm qu’elle doit utiliser notre java agent
transformer.jar. Bien entendu, il faut rajouter au classpath le jar
d’asm :
java -javaagent:transformer.jar -cp asm-1.5.3.jar:. fr/umlv/bmathus/MonTest
Et ça affiche :
[Loading fr/umlv/bmathus/MonTest]
Nom de la classe analysée = fr/umlv/bmathus/MonTest
[Loading fr/umlv/bmathus/MyClassOfMe]
Nom de la classe analysée = fr/umlv/bmathus/MyClassOfMe
Liste des interfaces de class fr.umlv.bmathus.MyClassOfMe :
interface java.io.Serializable
interface java.util.EventListener
Fin
Gagné ! Nous avons réussi à ajouter l’interface qu’on voulait à notre
classe.
Conclusion
ASM est très puissant, je n’ai fait là qu’en découvrir un petit morceau.
Ce type de traitement est applicable à n’importe quel attributs de votre
classe, vous pouvez ainsi modifier méthodes, champs, etc. Je reviendrai
certainement sur le sujet d’ici quelques temps si je trouve des
compléments intéressants à ce billet.
Fri, Jan 21, 2005
Vous vous souvenez du
billet où je
parlais du fait qu’on essayait de faire passer les brevets logiciels
lors d’un conseil de l’agriculture ?
Et bien, ils remettent ça ces
connards ! Putain, mais dans
quel monde je, nous vivons ? C’est ça qu’on appelle un processus
démocratique ? Essayer de faire passer en force (et plusieurs fois!!!)
des trucs aussi importants en les faisant valider par des gens qui ne
connaissent pas les dossiers et qui n’en ont donc pas les compétences ?
Ahurissant, désarmant. Je ne comprends pas. Je suis dégoûté. Partagé
entre l’envie de coller une grosse baffe aux instigateurs de cette
manipulation malhonnête et celle de leur vomir dessus par dégoût.
Quelle honte.
Wed, Jan 19, 2005
Des journées plus longues… Une soixantaine d’heures par jour m’irait
assez bien… Pas le temps de dormir… Pas le temps de sortir… Pas le
temps de vous parler…
Quelle horreur…
Wed, Jan 12, 2005
Un peu de technique, soyons fou. Après cette nuit de coding party pour
le projet de génie logiciel de mon école,
je me dis que c’est sûrement un bon moment pour vous parler d’hibernate.
J’avais déjà un peu parlé d’hibernate
ici pour ceux qui se
demandent ce que c’est. En gros, c’est un outil qui permet de ne pas
s’occuper de la sauvegarde de ses objets dans une base de données. On
spécifie pour ce faire un mapping en XML indiquant les correspondances
entre le modèle relationnel (les tables de la BD) et le modèle objet
(les classes).
Je vais ici décrire comment effectuer un mapping many-to-many. Cet
exemple est ciblé pour ce besoin, si vous ne connaissez pas ce dont je
parle et que vous voulez en savoir plus, allez plutôt lire la
documentation de
référence. Je me
suis initialement basé sur cet
article
pour me documenter et réaliser mes tests.
Mon objectif est de donner un exemple simplifié au maximum, mais
complet. Par complet, j’entends donner le diagramme de classes et leurs
relations, le mapping de toutes les classes et tester quelques trucs qui
m’ont fait me poser des questions lorsque je cherchais.
J’ai prévu de faire ça sur différents sujets, je dirais que le prochain
que je ferai sera conditionné à la fois par les demandes en ce sens et
un peu par mon temps :-).
Les relations ?
On a une classe RCourse et une classe RStudent :
- un cours peut contenir plusieurs étudiants ;
- un étudiant peut assister à plusieurs cours.

Rappelons ici, qu’une relation de type n/n génère sur un schéma
relationnel une table intermédiaire qui contient justement les
associations RCourse/RStudent existantes. Nous décidons d’appeler cette
table rstudent_rcourse.
Le code
Voilà les deux classes Java correspondantes :
public class RCourse
{
int id;
String name;
Set<RStudent> students;
//Écrivez ici tous les setters et getters
[...]
}
public class RStudent
{
private int id;
private String firstName;
private String lastName;
//Écrivez ici tous les setters et getters
[...]
}
Un conseil pour les getters et setters. Si vous utilisez un IDE comme
eclipse, demandez lui de générer toutes les méthodes en protected. En
effet, cette visibilité suffit à Hibernate pour accéder à l’objet
persistant et vous permet de ne pas exposer forcément tous les setters
publics sur votre objet. Un peu à la manière de la configuration d’un
firewall : fermez tout, ne passez ensuite en public que les méthodes au
fur et à mesure des besoins.
Le mapping
La partie un peu moins facile que le reste est la description du set ,
mais le fichier reste quand même assez simple :
Pour RCourse :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="de.berlios.chronos.technique.hibernate.relations.RCourse"
table="rcourse">
<id name="id">
<generator class="native"/>
</id>
<property name="name" type="java.lang.String" />
<set name="students" table="rstudent_rcourse" lazy="false">
<key column="course_id"/>
<many-to-many class="de.berlios.chronos.technique.hibernate.relations.RStudent"
column="student_id" outer-join="true"/>
</set>
</class>
</hibernate-mapping>
Pour RStudent :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="de.berlios.chronos.technique.hibernate.relations.RStudent"
table="rstudent">
<id name="id">
<generator class="native"/>
</id>
<property name="firstName"/>
<property name="lastName"/>
<set name="courses" table="rstudent_rcourse" lazy="false">
<key column="student_id"/>
<many-to-many class="de.berlios.chronos.technique.hibernate.relations.RCourse"
column="course_id"/>
</set>
</class>
</hibernate-mapping>
Faisons remarquer l’attribut lazy (i.e. paresseux, oui, oui) de la
balise set. Il conditionne le fait qu’Hibernate cherchera ou non à
peupler directement les collections dépendantes à la récupération de
l’objet (exemple : si on récupère un cours, si il doit aller chercher
tous les étudiants de ce cours tout de suite), ou si il doit attendre
l’utilisation de la collection en question pour le remplir.
Génération du SQL
Grâce à SchemaExport, il est possible de générer directement tout le SQL
correctement au mapping ci-dessus. Vous pouvez vous inspirer de
l’exemple de script donné
ici
si vous avez des soucis avec ça. Si vous insistez, je pourrais
éventuellement envisager d’écrire quelques explications sur le sujet.
alter table rstudent_rcourse drop constraint FKDB6FCE57C276819F;
alter table rstudent_rcourse drop constraint FKDB6FCE57575ED7BF;
drop table rstudent_rcourse;
drop table rstudent;
drop table rcourse;
drop sequence hibernate_sequence;
create table rstudent_rcourse (
course_id int4 not null,
student_id int4 not null,
primary key (student_id, course_id)
);
create table rstudent (
id int4 not null,
firstName varchar(255),
lastName varchar(255),
primary key (id)
);
create table rcourse (
id int4 not null,
name varchar(255),
primary key (id)
);
alter table rstudent_rcourse add constraint FKDB6FCE57C276819F foreign key (student_id) references rstudent;
alter table rstudent_rcourse add constraint FKDB6FCE57575ED7BF foreign key (course_id) references rcourse;
create sequence hibernate_sequence;
Tester
Après avoir écrit le code, le mapping, généré le sql et créé les tables,
on peut tester. Il est notamment intéressant de tester l’influence du
paramètre lazy.
Conseil : sauvegardez des étudiants et un cours auquel ils assistent.
Essayez ensuite de récupérer l’objet RCourse en mettant tour à tour le
paramètre lazy à false ou true.
Positionnez l’affichage du code SQL (hibernate.show_sql=true) et
faites quelques affichages entre la récupération de l’objet et le
premier accès que vous faites à la collection de students. Selon le mode
(fainéant ou non), vous remarquerez que le moment où la requête de
jointure est effectuée diffère : au moment du Session.find() pour
lazy=“false”, au premier accès à la collection pour lazy=“true”.
Conclusion
Bon, j’ai été un plus court que je n’aurais voulu, mais je suis très
fatigué et je pense que des docs plus complètes existent déjà sur
Internet. De toute façon, mon objectif résidait plus dans le fait de
founir tout le code nécessaire aux tests au néophyte afin qu’il puisse
constater simplement et rapidement le fonctionnement d’Hibernate.
Sun, Jan 9, 2005
Et hop, encore une faille de sécurité “extrêmement critique”… Via
slashdot, on
apprend en effet qu’une nouvelle faille
IE6
sur un SP2 pourrait permettre à un programme, je cite :
- d’écrire des fichiers sur votre disque ;
- d’en supprimer.
Étonnant, non ?
Bon, j’ai failli arrêter là ce billet, mais je me dis que certains (il
en reste encore ?) n’utilisent ou ne savent pas quelle autre solution
existe. Pour surfer sur Internet, utilisez
firefox.
Sun, Jan 9, 2005
Aujourd’hui, j’ai pris le train pour aller à Lille voir un copain à qui
je l’ai promis depuis la terminale. 6 ans déjà :-/. Il était temps…
Bref, en arrivant gare du Nord, avant de monter dans le train, il y
avait déjà une horde de contrôleur (une petite horde il est vrai,
environ 6 ou 7, c’est à partir de combien une horde ? (o= ) qui
attendait pour contrôler, donc, tous les voyageurs à l’entrée du quai.
Très bien, je montre mon billet, il le vérifie, me dit même que j’ai
oublié de le composter (ah, la fatigue…), je corrige ceci et je passe.
Je m’installe donc dans le convoyeur raillique de viande humaine et je
commence à travailler sur mon portable. Je découvre un réseau Wifi, je
m’y connecte, ehehe un DHCP me donne une adresse et … trop tard, le
train a démarré et je n’ai pas le temps de regarder si je pourrais avoir
accès au net =).
Après quelques minutes, qui vois-je arriver ? LE CONTRÔLEUR…
Je réfrenne une envie sauvage de lui demander pourquoi on est contrôlé
deux fois pour le même prix et s’il est payé au nombre de contrôles dans
la journée, il REvérifie donc mon billet et passe son chemin.
Alors question à un éventuel connaisseur des mécaniques occultes
internes à la SNCF : Est-ce que vous pouvez m’expliquer l’utilité de
payer des gens à contrôler les voyageurs dans le train alors que ceux-ci
n’ont déjà pu y rentrer que s’ils avaient été contrôlés ?
….. Et c’est nous qui payons …..
Thu, Jan 6, 2005
C’est
mieux.
# free
total used free shared buffers cached
Mem: 775264 607272 167992 0 29268 303304
-/+ buffers/cache: 274700 500564
Swap: 491360 0 491360
Avec Gnome 2.8, Eclipse, OpenOffice.org, Firefox, Thunderbird, Apache,
PostgreSQL et tous mes services habituels (serveur ssh, xinetd, etc.)
lancés en même temps.
C’est un peu comme une bouffée d’air pur :-)
Mise à jour du 9 janvier : pour les gens comme
Vincent qui ne comprennent pas ce qu’il y a au
dessus. En gros, ma machine avait trop peu de RAM (cf. billet
précédent),
ce qui fait qu’elle devait beaucoup swapper (en gros aussi, utiliser le
disque dur comme ram virtuelle, le problème c’est qu’un dd est beaucoup
plus lent que de la vraie RAM). La commande free donne l’utilisation de
la mémoire, ici on voit que j’ai maintenant 775Mo de RAM au lieu de 256
avant. On voit donc aussi en conséquence que la machine ne swappe plus
(au lieu de 300Mo avant…), ce qui se ressent énormément au niveau de
la réactivité du système lorsqu’il est chargé.
D’autres part, je vais rajouter ces quelques mots pour que google les
référence au cas où des gens qui possèdent le même portable que moi
chercheraient à savoir quelle RAM fonctionnerait avec leur ordinateur.
Mon portable est un Presario X1000, plus précisément un X1005EA. C’est
un centrino et le type de RAM qu’il utilise est de la DDR-SDRAM SoDimm
PC2100. J’ai acheté une barette de 512Mo Corsair garantie à vie sur le
site de LDLC, livré en quelques
jours seulement, tout a fonctionné parfaitement.
Thu, Jan 6, 2005
Bon, nous avons quelques
jours pour
que ce célèbre navigateur
obsolète
devienne premier sur google pour la requête
“navigateur
obsolète”.
Vincent et
Denis
ont commencé, je continue. Allez, faites passer le message pour que
notre navigateur
obsolète
possède enfin la place qu’il mérite tant.
C’est vrai qu’un navigateur
obsolète
comme celui-ci, on n’en fait plus…
Mise à jour du 9 janvier : Ayè, c’est déjà
gagné…