The current tutorial I am working on, the NetBeans Platform Paint Tutorial makes a lot of use of it too.
For example, in the Paint Tutorial for 7.0, you have this annotation:
@Messages({"# {0} - image","UnsavedImageNameFormat=Image {0}"})
public PaintTopComponent() {
setDisplayName(UnsavedImageNameFormat(ct++));
}
That replaces this:
String displayName = NbBundle.getMessage(
PaintTopComponent.class,
"UnsavedImageNameFormat",
new Object[] { new Integer(ct++) }
);
setDisplayName(displayName);
The "ct" referred to above is:
private static int ct = 0; //A counter you use to provide names for new images
At this point, I am able to save the file, the red error marks disappear (definitely in the latest dev builds), and then I can switch to the Files window and then browse to the generated "build/classes-generated/org/netbeans/paint/Bundle.java" file. There I find a static string for each key=value pair in my @Messages annotations. In the case of the above, I see:
static String UnsavedImageNameFormat(Object image) {
return org.openide.util.NbBundle.getMessage(Bundle.class, "UnsavedImageNameFormat", image);
}
Then, when I browse to "build/classes/org/netbeans/paint", I see that a Bundle.properties file has been created, which has, among other keys, these:
# {0} - image
UnsavedImageNameFormat=Image {0}
I can see that this approach has a number of advantages, especially that I can stay within my Java source file while coding, i.e., no need to switch to a properties file because that file will be generated. I asked Jesse (who created the annotation) to list what he sees as the advantages and here they are:
Your @Messages annotations can be moved very close to their actual usage. If the language supported it, we would just write something like
setDisplayName(@Localized("node_label") "My Node");
and some magical source transformation would replace the string constant with a call to NbBundle; unfortunately this is not possible. But at least the messages can be within a few code lines of their usage. This makes it easy to see (or edit) the actual value while reading the code; formerly you needed a special tool (Java Ext Editor) to "see" localized values during code browsing, which only worked in the IDE and only with this plugin installed.
Another rarer issue was people computing keys rather than using string constants to make code a little shorter, e.g. NbBundle.getMessage(X.class, "LBL_" + type + "_node"). Nice enough when it is right, but produces an MRE if the 'type' starts taking on an unforeseen value after some refactoring. With @Messages, you have to use a switch statement (or similar) on the 'type' - more verbose perhaps, but forces you to pay attention to the possibility that it has some unexpected value, and react gracefully.
Preventing these issues required that you search for all usages of NbBundle in any code you were deleting or moving, collect a list of all the referenced keys, Ctrl-F search for all mentions of those keys in the package, and deal with them (delete or move or copy). And even being that careful could fail if there were computed keys (as above) - or if people used NbBundle.getMessage(ClassInAnotherPackage.class, "...") so that your naive text search missed some usages! All these problems are impossible if @Messages is used exclusively: keys are only produced corresponding to live Java code, and can only be consumed when they really exist (by code in the same package as the definition).
Compare that to the benefits of statically typed languages in general - i.e. all of the above mistakes applied to plain old method calls are common enough when using, say, Python. Proponents of dynamic languages point out that you can catch most such mistakes by diligent testing, but I have seen plenty of runtime errors of this kind in released versions of Mercurial despite a big test suite. Anyway forgoing the benefits of a compiler is like not wearing a seatbelt because you are convinced you are a perfect driver!
Finally, this annotation should have no adverse impact on localization since the output JAR still has a Bundle.properties per package with the same contents as before, including comments directed at localizers.
Yeah ! I have to admit this is the first annotation I really understand and like and certainly will use. In general (most likely out of ignorance) I am concerned with all those annotations and that my Java code will look more like a scripting language than Java. But this one is really nice - thanx Jesse !
Can you explain how localization works with the @messages annotation? Where should the localized bundle be placed?
The problem is that using custom code the designer will only show "User Code". To avoid that, i'm using a mixed approach at the moment: i'm using the annotation to generate key/value pair, but inside initComponent() method i'm still using NbBundle.getMessage(...). This breaks one of the annotation's primary objectives: compile time safety.
TIA
This example isn't working in NB 7.01
it gives me an underline error in setDisplayName then prompts me to create a method UnsavedImageNameFormat that takes an int.
I'm also seeing that problem:
PaintApp\Paint\src\org\netbeans\paint\PaintTopComponent.java:45: cannot find symbol
symbol : method LBL_Clear()
location: class org.netbeans.paint.PaintTopComponent
The same goes for LBL_Foreground, LBL_Brushsize, and UnsavedImageNameFormat.
Hi, is there a manner to use this annotation (or a similar one) taking the messages directly from a Bundle.properties file manually created? I don't like much the fact that all the strings must be written in the sourcecode.