Un ex-collègue, tout récent Googler à Londres, m’a fait parvenir le dernier produit de Google. Le premier qui, visiblement, n’est pas lancé en version beta : Google Cahuètes. Merci Cyril. Ca doit être la panique chez Bénénuts !

Un ex-collègue, tout récent Googler à Londres, m’a fait parvenir le dernier produit de Google. Le premier qui, visiblement, n’est pas lancé en version beta : Google Cahuètes. Merci Cyril. Ca doit être la panique chez Bénénuts !


It’s been a while that I wanted to try Git. At work, I’ve used CVS, Merant PVCS, MS SourceSafe and even the worst SCM ever conceived: Serena Dimensions. For the last few years, I’ve been using Subversion at work and at home, and it hasn’t been too hard. The only difficulties I’ve had with Subversion were all related to merging, and merge tracking. One of the few things you always hear about git is how branching is so easy, and how merging is a breeze. So I wanted to give git a try, even if the distributed nature of git has always seemed like a feature nobody cares about (at least in the enterprise).
First of all, I must mention the Pro Git book (freely available online), which does miracles in explaining how git works. But even with the help of the book, git has a learning curve. Fortunately, I’m not an early adopter, and git already has good support: msysgit and TortoiseGit work pretty well on Windows.
I’m only at the beginning and, unfortunately, I can’t use it on a medium-sized multi-developer project yet, but some of the aspects of Git already seem just right compared to SVN :

I’ve been caught several times on the same bug recently : some method takes a collection as argument, and must execute a SQL (actually, a Hibernate query) to find all foos having their ID in the given collection:
public List<Foo> findByIds(Set<Long> ids) {
String hql = "select foo from Foo foo where foo.id in (:ids) order by foo.bar";
Query query = session.createQuery(hql);
query.setParameterList("ids", ids);
return query.list();
}
This generates a SQL query looking like
select f.ID, f.BAR from FOO f where f.ID in (?, ?, ?) order by f.BAR
Everything goes well, until the method is executed with an empty set as argument. The SQL query becomes invalid and a runtime exception is thrown. Could anyone tell me why the SQL standard doesn’t allow an IN without any parameter in the list? It’s not like the SQL engine couldn’t optimize the query and return nothing instantly.
By the way, Hibernate could also play a role here, and translate an no-parameter IN clause by WHERE 0 = 1. But no, you have to take care of this yourself, and rewrite the method as this:
public List<Foo> findByIds(Set<Long> ids) {
if (ids.isEmpty()) {
return Collections.emptyList();
}
String hql = "select foo from Foo foo where foo.id in (:ids) order by foo.bar";
Query query = session.createQuery(hql);
query.setParameterList("ids", ids);
return query.list();
}
This is easy when the method is as simple as this one. But when the IN clause is embedded inside a subquery, things aren’t always as straightforward. Why an empty set is considered invalid in SQL is an mystery. But at least, SQL doesn’t force me to write
select f.ID, f.BAR from FOO f where f.ID = ? order by f.BAR
each time the set contains only one element.
Eric Woerth est attaqué de toute part en ce moment, mais ces attaques et sa défense, pour ce que j’en ai entendu, sont exclusivement axées sur son honnêteté (ou sa malhonnêteté, c’est selon), sur l’aspect légal, et sur le conflit d’intérêt qu’il y a entre ses positions de ministre, trésorier de l’UMP, et mari d’une gestionnaire de la fortune de Liliane Bettencourt.
Personnellement, c’est sur un axe moral que je l’attaquerai.
A mon sens, l’intérêt général devrait représenter l’alpha et l’omega d’un ministre du budget ou du travail. Or Eric Woerth partage la vie (et donc, j’imagine, les valeurs) d’une femme dont le métier est de mettre tout en oeuvre pour que la femme la plus riche de France, qui ne manquera jamais de rien, qui ne sait que faire de son argent tant elle en a, paie le moins possible d’impôt. Que cela aille jusqu’à l’évasion fiscale illégale, ça n’a pas vraiment d’importance : l’impôt, c’est l’intérêt général. Vouloir y échapper, c’est être contre l’intérêt général. Pour le ministre Woerth, le nombre de zéros sur le compte en banque de la femme la plus riche de France compte plus que l’intérêt général. C’est ça qui est inacceptable, et devrait forcer la démission du ministre.
What I like about Linux is that the install CDs are also live CDs.
Every once in a while, I decide to try out the latest linux distro that will conquer the desktop market. Having a live CD is really useful : you don’t have to install anything. No risk corrupting your hard disk and losing everything installed and running like a charm (under Windows). But the end result is always the same : after some time trying to get everything up and running, reading obscure forum posts with countless lines of CLI commands and config files to update, I abort, and go back to my working Windows install.
This time, I received an old NEC laptop, with only 256 Mo of RAM, Windows XP installed on it and running, but slowly. Since my girlfriend wanted a laptop just to be able to download some pictures and take them anywhere she wants, I thought a lightweight version of Linux would be a good replacement of XP. Since this laptop is really old, hardware support would not be a problem. I downloaded the latest version of Xubuntu, supposed to be running fine with only 256Mo, and tried in on the laptop, using the live CD.
Everything ran fine until I tried to connect to my wifi network. A small icon indicated that I had no connection, although wireless networking was enabled. The problem seems to come from a button on top of the keyboard, which enables or disables the wireless network card. Pressing the button changed nothing : the led stayed off, and no networking was possible. I thus googled, already thinking : why is Linux unable to handle a network switch on a 5 year old laptop?
Anyway, I found forum posts indicating that rfswitch was perhaps the solution. So I downloaded it, put it on a USB stick, and plugged the stick into the laptop. Nothing happened. A quick look at the Removable Media parameters : yes, removable media are supposed to be mounted automatically at insertion. But nothing happened. Great!
So I plugged an ethernet cable into the laptop, and downloaded rfswitch. Look at this project. No description of what it does and how it works, and a dowload which gives you two C files, a Makefile, and a README giving no instruction whatsoever on how to install and run the « software ».
So after two hours trying to find a solution to a problem that should not exist, after having discovered that USB sticks didn’t work, after Firefox crashed twice, I decided that I would stay with Windows and wait for the next linux distro that would conquer the desktop market.
C’est l’une des mantras du Dr. House : « les patients mentent ». En plongée, c’est la même chose : les élèves mentent.
Je reviens de mon premier stage technique de formation de N2 en tant qu’encadrant, et c’est ce que j’ai pu constater. Le premier jour était notamment dévolu au test du lestage. Une tendance générale des plongeurs est de se sur-lester. C’est particulièrement vrai des plongeurs peu expérimentés, qui maîtrisent mal leur ventilation. Deux tests classiques peuvent donc être réalisés.
Premier test : au début de la plongée, avant de s’immerger, on dégonfle les gilets, on respire normalement, et l’eau doit atteindre approximativement le milieu du masque. Si on coule ou que l’eau arrive plus haut, c’est qu’on est sur-lesté.
Deuxième test : en fin de plongée, au palier, le plongeur doit se maintenir à niveau, stab vide, en respirant normalement.
Le problème de ces deux techniques est le même. Elles visent à corriger un sur-lestage. Ce sur-lestage est provoqué par un manque, voire une absence totale, de maîtrise de la ventilation. Or ces deux techniques requièrent la même capacité du plongeur : respirer normalement dans l’eau. Posez la question à un plongeur peu expérimenté en début de stage : il vous répondra qu’il respire normalement alors qu’il est en « ventilation haute ». Les poumons sont pleins, et il ne les vide que légèrement pour respirer.
Conclusions : cet exercice doit impérativement, à mon sens, se préparer calmement avant de se mettre à l’eau, pour que l’élève puisse évaluer plus correctement son niveau de ventilation. Il doit aussi être répété au cours du stage. Le stress diminuant et l’assurance augmentant, la ventilation aura tendance à se stabiliser et à se normaliser, et de nouveaux kilos supplémentaires pourront être enlevés, pour le confort et la sécurité du plongeur et de sa palanquée.
In a previous article, I explained how to get the code coverage data generated by Cobertura, for integrations tests of an application deployed on Google App Engine.
Cobertura just released a new version (1.9.4), which is supposed to be up to 10 times faster (kudos for that), but is not backward compatible. I thus had to rewrite the method to get the project data. Here it is.
public Resolution flushCobertura() throws ClassNotFoundException,
SecurityException,
NoSuchMethodException,
IllegalArgumentException,
IllegalAccessException,
InvocationTargetException,
IOException,
InstantiationException {
String projectDataClassName = "net.sourceforge.cobertura.coveragedata.ProjectData";
Class< ?> projectDataClass = Class.forName(projectDataClassName);
Object projectData = projectDataClass.newInstance();
String touchCollectorClassName = "net.sourceforge.cobertura.coveragedata.TouchCollector";
Class< ?> touchCollectorClass = Class.forName(touchCollectorClassName);
String methodName = "applyTouchesOnProjectData";
java.lang.reflect.Method applyTouchesOnProjectDataMethod =
touchCollectorClass.getDeclaredMethod(methodName, new Class[] {projectDataClass});
applyTouchesOnProjectDataMethod.invoke(null, projectData);
getContext().getResponse().setContentType("application/octet-stream");
OutputStream out = getContext().getResponse().getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
try {
oos.writeObject(projectData);
}
finally {
oos.close();
}
return null;
}
Les élections régionales approchent, et une fois encore, je n’irai pas voter.
Et pourquoi donc ? Parce que je n’en ai pas le droit. Et oui, j’habite en France depuis plus de neuf ans maintenant, mais je suis toujours Belge, et je n’ai donc pas le droit de voter, ce qui me fait enrager.
Tout d’abord, il faut noter que cet état de fait est, me semble-t-il, général. Je n’ai pas connaissance d’une démocratie dans le monde qui autorise ses résidents étrangers à voter. Cela inclut bien sûr la Belgique, qui souffre du même déficit démocratique que la France. Pas question de me plaindre d’un manque de réciprocité. Là n’est pas le problème.
Ça n’est pas tout à fait exact cependant. Au sein de l’Union Européenne, les membres de l’Union ont le droit de voter dans le pays où ils résident, mais uniquement aux élections européennes, et aux élections locales (municipales en France, communales en Belgique). Certains pays (dont la Belgique, mais pas la France), accordent le droit de vote aux élections locales à tous les étrangers, même non résidents de l’UE. Cela signifie donc qu’on considère à présent qu’il est normal qu’un résident prenne part aux choix concernant les ronds-points de sa commune, ses crèches, l’école primaire que fréquente ses enfants, ses logements sociaux. En revanche, pas question pour lui d’avoir son mot à dire sur la politique de transport, la politique économique et sociale, la justice, ni même sur le collège et le lycée que fréquentent ses enfants.
Il y a donc deux classes de citoyens en France. Tous ont les mêmes devoirs (respecter la loi, payer ses impôts et ses charges sociales, répondre de ses actes devant la justice), mais tous n’ont pas les mêmes droits.
Pourquoi est-ce ainsi ? Je n’ai pas trouvé de réponse satisfaisante. L’explication est à mon avis historique: les fondements de la démocratie ont été établis lorsque la mobilité était quasi inexistante. On naissait quelque part, et on y restait jusqu’à la fin de sa vie. A l’heure où la mondialisation, l’Europe et la mobilité sont sur toutes les lèvres, il serait temps à mon avis d’accorder les principes démocratiques avec la réalité.
J’ai bon espoir cependant. Le droit de vote a fini par être accordé aux pauvres, puis aux femmes, puis aux SDF. L’histoire finira par l’accorder aux étrangers.
Aidons-là un peu, et examinons les arguments contre une telle évolution.
C’est normal que tu ne puisses pas voter. Tu n’es pas citoyen français.
Si la citoyenneté se résumait à la nationalité, je serais d’accord. Wikipedia décrit la citoyenneté par ces mots :
La citoyenneté est le fait pour une personne, pour une famille ou pour un groupe, d’être reconnu comme membre d’une cité (aujourd’hui d’un État) nourrissant un projet commun et qu’ils souhaitent y prendre une part active. La citoyenneté comporte des droits civils et politiques, et des devoirs définissant le rôle du citoyen dans la cité et face aux institutions. Au sens juridique, c’est un principe de légitimité : un citoyen est un sujet de droit.
Il me semble que cette définition s’applique parfaitement à ma situation, hormis justement cette absence de droit de vote. Elle ne s’applique par contre pas à de nombreux Français qui ont pourtant le droit de vote, mais qui, en s’évadant fiscalement, refusent de prendre une part active au projet commun.
Pourquoi t’accorder le droit de vote alors que tu peux retourner en Belgique dès que tu veux ?
N’importe quel Français peut également aller vivre en Belgique dès qu’il le veut. Par ailleurs, refuse-t-on à un Français d’origine bretonne le droit de vote aux régionales d’Ile-de-France s’il y réside ? Pourquoi alors refuser le droit de vote à un étranger qui a choisi de vivre en France ?
Si tu veux voter en France, pourquoi ne demandes-tu pas la nationalité française ?
C’est sans doute ce que je vais finir par faire si ce droit continue à m’être refusé. Il n’en reste pas moins que le déficit démocratique reste entier. Si demain je partais à Londres, je trouverais logique de voter en Angleterre. Et j’arrêterais bien sûr de voter en France, puisque ce vote n’aurait aucune incidence sur moi. Je ne vote d’ailleurs plus en Belgique depuis que j’habite en France. Cela ne me concerne plus.
Internationalization (i18n in short) is a tedious task. You need to be meticulous, and to know enough about some foreign languages and cultures in order to avoid making bad assumptions.
A common mistake made by beginners is to use concatenation to format parameterized messages. It not only makes the process harder than necessary, but it also makes it impossible to translate some sentences.
For example, if you try to internationalize the sentence January 3 is the third day of the year, where January, 3 and third are runtime parameters of the message, the naive option is to concatenate :
${month} ${day} <fmt:message key="isThe"/> ${position} <fmt:message key="dayOfTheYear"/>
The problem is that this sentence, in French, should be translated this way: Le 3 janvier est le troisième jour de l’année. The order of the runtime parameters is not the same in French and in English. The solutions is obvious: use parameterized messages.
<fmt:message key="dayOfTheYearSentence">
<fmt:param value="${month}"/>
<fmt:param value="${day}"/>
<fmt:param value="${position}"/>
</fmt:message>
In the English resource bundle, you’ll find the following entry:
dayOfTheYearSentence=${0} {1} is the {2} day of the year
and the French resource bundle will contain this entry:
dayOfTheYearSentence=Le {1} {0} est le {2} jour de l''année
If this example may seem obvious, more subtle mistakes can happen. For example, do you know that you’ll write $50 in English, but 50 $ in French? Do you know that a space must be placed before the colon symbol in French, but not in English? That’s why the complete form labels, including the colon, should be in the resource bundles:
# English resource bundle nameLabel=Please enter your name:
# French resource bundle. A non-breaking space is used before the colon, # to make sure it doesn't end up alone on a new line nameLabel=Veuillez saisir votre nom :
In the previous example, you noticed that the quote before année had been doubled. This is necessary because the JSTL uses java.text.MessageFormat under the hood. And this class requires that quotes be doubled. This is really confusing to translators, because they don’t know why and when quotes must be doubled. It’s so confusing that the Javadoc for MessageFormat has this warning:
Warning:
The rules for using quotes within message format patterns unfortunately have shown to be somewhat confusing. In particular, it isn’t always obvious to localizers whether single quotes need to be doubled or not. Make sure to inform localizers about the rules, and tell them (for example, by using comments in resource bundle source files) which strings will be processed by MessageFormat. Note that localizers may need to use single quotes in translated strings where the original version doesn’t have them.
Another thing that many persons have a hard time to catch is that some things depend on the language of the user, and some depend on the country of the user. The confusion is reinforced by all these web pages that use country flags to let the user choose his preferred language. Hey! French is not spoken only in France. And even more important: some countries have several official languages. Belgium, for instance, has Dutch, French and German as official languages.
Another gotcha to be aware of when i18ning a web app is that the JSTL and the java.util.ResourceBundle class don’t obey the same rules regarding the choice of the resource bundle. Let’s say that my web app has two resource bundles: app.properties where the default (English) messages are set, and app_fr.properties where the French messages are set. Let’s say that my web app is deployed on a French server, where the system locale of the JVM is thus fr.
If the user’s locale is set to English (en), the JSTL will look for messages in app_en.properties (not found) then in app.properties, and you’ll end up, as expected, with an English message.
However, if you call, in your Java source code,
ResourceBundle.getBundle(user.getLocale()).getString("myKey");
you’ll end up with a French message. The reason is that ResourceBundle.getBundle() first looks up for properties files with the system locale (French) before falling down to the default properties file.
The trick is obvious to get the same behavior: add an empty app_en.properties file to the web app. This way, an English resource bundle will be found by ResourceBundle.getBundle(), but every lookup in this bundle will fall down the the parent, default, properties file.