Wednesday April 30, 2008
Introducing "Feature on Demand"
As your application gets larger, the end user experiences two problems: the download size increases and performance slows down. Not necessarily so, but the likelihood of these problems increases as time goes by. That's where modular systems come in handy, because they let the end user choose the modules they want while discarding all those they don't need. However, that process is problematic. How does the end user know which module provides which features? And then there's the problem of the ui that you provide for making features available, which is often not optimal for all your users. Sure, you could let the application automatically install all features and all updates to all features. That might be useful for many of your users. However, not all users want all features and all updates. On the one hand, these users would like to be able to select which features (and which updates to features) they want, but they also want the process to be really simple.
Welcome to "feature on demand". That's a new NetBeans Platform concept that's in the process of being evolved. It aims to enable you to provide the user with a really lightweight application (i.e., very small download, very small footprint, very small number of modules, very small everything). However, despite the lightness of the application, all/most of its ui is already available. Then, whenever the end user wants to use a feature in the application, such as a particular window, the application will automatically install that window, in the background. How? By making a menu item (or other entry point) available which, when invoked, causes the related module to download in the background, install itself automatically into the application, and immediately make its ui elements (such as a new window) available to the end user.
Potentially three parties are involved in this process:
- The end user. I.e., granny Smith at home, filling in her tax returns in an application created on top of the NetBeans Platform.
- The "feature on demand" service consumer. This is the developer who makes use of an existing entry point in the application by plugging into it. For example, let's say the developer writes a module and puts it into an update center. The module provides a new window in the application. In a separate module that is part of the application's official distribution (i.e., or that is installed automatically, or in some very simple way) the developer specifies that the module should be installed on demand, i.e., only when granny Smith invokes the related menu item. This module, i.e., the module consuming an entry point, is very small (just one or two XML files, as you can see in the example screenshot below), whereas the module (or multiple modules, including external libraries) providing all the functionality is much larger. Hence, distributing entry point consumers with your application is much more economical than distributing fully fledged features. In effect, you're distributing fake ui (such as a menu item that appears to invoke a window, while in fact it also installs it), while the real feature will be installed when the fake ui is used.
- The "feature on demand" service provider. By default, "feature on demand" entry points exist (at least, currently) for new actions and new project types. That means that the service consumer can make use of these two entry points out of the box. However, potentially, you'd want additional entry points for your own features. Possibly your feature is not related to a menu item or project type. For example, when granny Smith tries to hyperlink in NetBeans IDE from one location to another, the module that provides that feature could be installed in the background, if the service provider provides such an entry point to the service consumer. The service provider is not concerned with specific modules, i.e., the service provider doesn't know what modules the service consumers will want to make available. The service provider simply makes entry points into the application available. This person will have a very technical knowledge of the application, while the service consumer needs to do nothing other than provide XML files that define how the entry point should be consumed in a particular case. The service provider and the service consumer could be the same person. And, of course, granny Smith at home could be all three at once, although this is less likely.
The end user, i.e., granny Smith, only gets involved in this whole cycle when she selects the menu item (or whatever the service provider makes available to the service consumer to let granny Smith demand the feature). But what about the service consumer? How does the service consumer consume the entry point? Let's say the service consumer wants to make the Module Manager available to granny Smith, but only when granny Smith selects the menu item to invoke it. Note that the Module Manager is a window in the IDE, provided by a module that's in the Update Center in 6.0 and 6.1, but never installed by default. Here's all that the service consumer needs to do to provide a fake "Module Manager" menu item in the Window menu which, when clicked, will install the Module Manager in the background:
Here's the content of the "modulemanager.xml" file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
//We want to contribute a new menu item to the application:
<folder name="Menu">
//The new menu item will be within the Window menu:
<folder name="Window">
//The unique identifier of the menu item:
<file name="org-yourorghere-modulemanager-ModuleManagerAction.instance">
//The reference to the API to which we delegate the creation of the menu item:
<attr name="instanceCreate" methodvalue="org.netbeans.spi.actions.support.Factory.delegate"/>
//The reference to the service provider's action that will be invoked when the menu item is created:
<attr name="delegate" methodvalue="org.netbeans.modules.autoupdate.featureondemand.api.Factory.newAction"/>
//The module that will be installed when the menu item is selected:
<attr name="codeName" stringvalue="org.netbeans.modules.modulemanager"/>
//The localization bundle that contains the display name of the menu item:
<attr name="SystemFileSystem.localizingBundle" stringvalue="org.yourorghere.modulemanager.Bundle"/>
//The key in the localization bundle that defines the display name of the menu item:
<attr name="ActionName" stringvalue="org-yourorghere-modulemanager-ModuleManagerAction" />
//The position of the menu item within the Window menu:
<attr name="position" intvalue="10"/>
</file>
</folder>
</folder>
</filesystem>
The above looks a lot like a layer.xml file, doesn't it? That's because that's what it is. It is known as the "delegate layer file", which is injected into the System FileSystem under certain conditions. Which conditions? The conditions are specified in the layer.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
//The official "feature on demand" folder in the System FileSystem:
<folder name="FeaturesOnDemand">
//The unique name of this entry in the "feature on demand" folder:
<file name="org-yourorghere-modulemanager-ModuleManagerAction.instance">
//The API class that holds the delegate layer file, with related information:
<attr name="instanceCreate" methodvalue="org.netbeans.modules.autoupdate.featureondemand.api.FeatureInfo.create"/>
//The presence of this plugin determines whether the delegate layer will be injected
//i.e., if this plugin is not installed, the menu item will be shown, and vice versa,
//this plugin could potentially be different to the plugin that the menu item will install:
<attr name="codeName" stringvalue="org.netbeans.modules.modulemanager"/>
//The location of the delegate layer file:
<attr name="delegateLayer" urlvalue="nbresloc:/org/yourorghere/modulemanager/modulemanager.xml"/>
</file>
</folder>
</filesystem>
So, now, granny Smith, filling in her tax returns, would see "Module Manager" in the Window menu (if the Module Manager plugin isn't already installed). She would, if she selects the Module Manager menu item, see that the related plugin installs and then immediately makes the Module Manager available. Until that point, the Module Manager will not be installed in the application and thus will not be weighing down the application, in terms of download size, footprint, and performance. By the way, all of this assumes that the plugin that will be installed on demand is actually available in one of the registered update centers.
Everything required for implementing "Feature on Demand" is already available in the "contrib" module in the NetBeans sources. For more information on this, together with several demos, including how to provide a new entry point, come to the JavaOne BOF "Toward a Consumer IDE: Get What You Want When You Want It (BOF 5091)", on Wednesday 18.30-19.20. I will be presenting this BOF together with the engineer who conceptualized and implemented "Feature on Demand", the NetBeans update center guru Jiri Rechtacek.
Apr 30 2008, 04:05:52 AM PDT Permalink
org.openide.filesystems.FileUtil.findBrother
The org.openide.filesystems.FileUtil.findBrother method is typically used by the Matisse GUI Builder so that if a file named "xyz.java" is in the same folder as a file named "xyz.frm", the two are merged together and opened as one in the editor. Specifically, FileUtil.findBrother takes a FileObject and a file extension, which is the file extension of the "brother" file.
There are several other conceivable scenarios where this functionality might be handy, such as Wicket, which typically has "xyz.java" in the same folder as "xyz.html". The latter provides the markup for the former. Below you can see my point, the Java files that have a matching HTML file (i.e., the same name) have a Wicket icon merged with their Java icon:
So, first create a new DataObject for text/x-java—and make sure it will install before the standard Java DataObject. Then, use the DataLoader to branch the DataObject, depending on whether a "brother" is found:
@Override
protected MultiDataObject createMultiObject(FileObject primaryFile) throws DataObjectExistsException, IOException {
FileObject bro = FileUtil.findBrother(primaryFile, "html");
if (null != bro) {
//Return our own data object:
return new WicketDataObject(primaryFile, this);
}
//Return the standard data object:
return new JavaDataObject(primaryFile, this);
}
Finally, in the Node, use org.openide.util.Utilities.mergeImages to merge a small image on top of the standard Java icon:
private static final String WICKET_ICON_BASE =
"org/netbeans/findbrotherdemo/wicket_8x8.png";
private static final String JAVA_ICON_BASE =
"org/netbeans/modules/java/resources/class.gif";
@Override
public Image getIcon(int arg0) {
Image wicket = Utilities.loadImage(WICKET_ICON_BASE);
Image java = Utilities.loadImage(JAVA_ICON_BASE);
Image result = Utilities.mergeImages(java, wicket, 8, 8);
return result;
}
Install the module and then Java files with HTML brothers will have our Wicket icon merged with their Java icon.
Apr 29 2008, 04:11:39 AM PDT Permalink
Reorganized & Simplified Wicket Support
In preparation for a JavaOne demo, I've simplified the NetBeans/Wicket support, from a ui and user perspective. This is what the Frameworks panel now looks like, after I removed some superfluous options (so that now the header panel is always created, while the useless option for the dummy pojo is removed) and changed some default names:
And when the user finishes the wizard, they will see exactly this, i.e., the source package folder will be open automatically and the HomePage class will be open in the editor, because that's probably the first place where you'll start coding. A default model setting is defined in the generated HomePage class and the org.apache.wicket.markup.html.resources.StyleSheetReference class is used in the BasePage class, to provide localized CSS support. Notice below also that the default names of the generated files have been changed and simplified, so that it's easier to see what's what, especially if you have some Wicket background:
For example, as you can see, pages and panels are easily distinguishable, now, because the name of the generated file (by default, anyway) contains the related info in this regard. Finally, no index.jsp is created, for the first time. The IDE's Frameworks support creates an index.jsp file by default, if no welcome file element is defined in the web.xml file by the module. So, I defined a welcome file element (even though it isn't used by Wicket) and so now the index.jsp is no longer created.
I need to do a bit more work on the module, such as upgrading the libraries to Wicket 3.3 (which fixes at least one important Ajax-related bug that I am aware of) and I'm hoping to have committed all my changes to CVS by the time JavaOne begins.
Apr 28 2008, 08:27:46 AM PDT Permalink
Using Spring to Enable the Print Menu Item
Recently I blogged about Spring integration into the NetBeans module system. Here's how you would use that integration to enable the Print menu item for a TopComponent. In effect, what this example shows is how to use a Spring configuration file to extend a TopComponent's Lookup to include a PrintCookie:
- Implement PrintCookie, which requires a dependency on Nodes API:
package org.yourorghere.nbspringdemo1; import javax.swing.JOptionPane; import org.openide.cookies.PrintCookie; public class PrintImpl implements PrintCookie { @Override public void print() { JOptionPane.showMessageDialog(null, "I am printing..."); } } - Create a bean for your PrintCookie in your Spring app-config.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="printCookie" class="org.yourorghere.nbspringdemo1.PrintImpl"/> </beans> - Add to the TopComponent's constructor:
//Bring in Spring: String[] contextPaths = new String[]{"org/yourorghere/nbspringdemo1/app-context.xml"}; ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(contextPaths); //Convert Spring to Lookup: Lookup lookup = NbSpring.create(ctx); //Potentially, look up your JPanel and add to TopComponent: //Item<JPanel> item = lookup.lookupItem(new Template<JPanel>(JPanel.class, null, null)); //JPanel foo = item.getInstance(); //add(foo, java.awt.BorderLayout.CENTER); //Add node for PrintCookie: AbstractNode myNode = new AbstractNode(Children.LEAF, lookup); ProxyLookup proxyLookup = new ProxyLookup(Lookups.singleton(myNode), myNode.getLookup()); associateLookup(proxyLookup);
That's it. Now the Print menu item is enabled whenever the TopComponent is activated. You could narrow it down further, basing it on whether something in the TopComponent has changed.
Thanks to Jaroslav Tulach for showing me how to do this.
Apr 27 2008, 03:05:47 PM PDT Permalink
cismet Developers Will Code for Food!
The "Will Code for Food" competition takes us to... a small software company named cismet based in Saarland, a small state in Germany. The guys from cismet are the ones who created the "Log4J Logging Viewer plugin for NetBeans IDE". It is also known as BeanMill. On their Partners page, you can see that cismet is one of the NetBeans partners.
"Thanks for the nice idea," writes Sebastian Puhl, one of the cismet developers, "we had fun making the pictures for the competition." The whole series is in cismet's Flickr account. My personal favorite is this one:
Remember that the 1st Annual "Will Code For Food" Competition will end on Friday, 1 August 2008, and that the winner will receive a signed copy of Jaroslav Tulach's upcoming book on API design. Submissions to geertjan DOT wielenga AT sun DOT com.
Apr 26 2008, 08:44:03 AM PDT Permalink
NetBeans Lookup Example
Lookup example, producing a new message automatically every 2 seconds:
- Provider module:
public final class Selection { private Selection() { } private static MyLookup LKP = new MyLookup(); //Make the Lookup accessible: public static Lookup getSelection() { return LKP; } private static final class MyLookup extends ProxyLookup implements Runnable { private static ScheduledExecutorService EX = Executors.newSingleThreadScheduledExecutor(); public MyLookup() { EX.schedule(this, 2000, TimeUnit.MILLISECONDS); } private int i; @Override public void run() { //Add to the Lookup a new MyHello: setLookups(Lookups.singleton(new MyHello(i++))); EX.schedule(this, 2000, TimeUnit.MILLISECONDS); } } private static final class MyHello implements HelloProvider { private String text; public MyHello(int i) { text = i % 2 == 0 ? "Hello from Tom" : "Hello from Jerry"; } public String sayHello() { return text; } } }public interface HelloProvider { public String sayHello(); } - Consumer module, with dependency on the provider module:
final class HelloTopComponent extends TopComponent implements LookupListener { private static HelloTopComponent instance; private static final String PREFERRED_ID = "HelloTopComponent"; private Resultresult; private HelloTopComponent() { ... ... ... //We have a dependency on the provider module, //where we can access Selection.getSelection(): Lookup lookup = Selection.getSelection(); //Get the HelloProvider from the result: result = lookup.lookupResult(HelloProvider.class); //Add LookupListener on the result: result.addLookupListener(this); //Call result changed: resultChanged(null); } StringBuilder sb = new StringBuilder(); int i; @Override public void resultChanged(LookupEvent arg0) { long mills = System.currentTimeMillis(); Collection extends HelloProvider> instances = result.allInstances(); for (HelloProvider helloProvider : instances) { String hello = helloProvider.sayHello(); sb.append(mills + ": " + hello + "\n"); jTextArea1.setText(sb.toString()); } } ... ... ...
Result:

Apr 25 2008, 07:54:49 AM PDT Permalink
Vincent Cantin: "Will Show Hidden Folders For Food"
The first entry for the 1st Annual "Will Code For Food" Competition:
Possibly the above is a reference to The Mystery of the Hidden Folders in the Favorites Window and on the thread on which that blog entry is based.
So now the flood gates are open... more "code for food" photos are welcome at geertjan DOT wielenga AT sun DOT com.
Apr 24 2008, 11:41:00 AM PDT Permalink
Hello Code Generator
I click Alt-Insert and I see this:
Here's the code:
import com.sun.source.util.TreePath;
import java.io.IOException;
import java.util.Collections;
import javax.swing.JOptionPane;
import javax.swing.text.JTextComponent;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.modules.java.editor.codegen.CodeGenerator;
public class HelloGenerator implements CodeGenerator {
public static class Factory implements CodeGenerator.Factory {
public Factory() {
}
@Override
public Iterable<? extends CodeGenerator> create
(CompilationController controller, TreePath path) throws IOException {
return Collections.singleton(new HelloGenerator());
}
}
@Override
public String getDisplayName() {
return "Hello World";
}
@Override
public void invoke(JTextComponent arg0) {
JOptionPane.showMessageDialog(null, "Hello World!");
}
}
Doesn't do anything yet, but gives you a starting point. Register it like this:
<filesystem>
<folder name="Editors">
<folder name="text">
<folder name="x-java">
<folder name="codegenerators">
<file name="org-netbeans-modules-my-codegen-HelloGenerator$Factory.instance">
<attr name="position" intvalue="10"/>
</file>
</folder>
</folder>
</folder>
</folder>
</filesystem>
Dependencies: Javac API Wrapper, Java Editor (implementation dependency, because the above is not a public API yet), and Java Source.
Apr 23 2008, 12:46:38 AM PDT Permalink
Converting Spring to Lookup
In the screenshot below, the content of the "Spring Window" is injected via a Spring configuration file:
The highlighted classes above are the Swing components and behavior that Spring injects into the TopComponent (except for 'SpringAction', which opens the window). In the editor area above, the following constructor is shown:
private SpringTopComponent() {
initComponents();
setName(NbBundle.getMessage(SpringTopComponent.class, "CTL_SpringTopComponent"));
setToolTipText(NbBundle.getMessage(SpringTopComponent.class, "HINT_SpringTopComponent"));
String[] contextPaths = new String[]{"org/netbeans/nbspringdemo/app-context.xml"};
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(contextPaths);
Lookup lookup = NbSpring.create(ctx);
Item item = lookup.lookupItem(new Template(JPanel.class, null, null));
JPanel foo = (JPanel) ctx.getBean(item.getId());
add(foo, java.awt.BorderLayout.CENTER);
}
The line in bold above is possible as a result of the new Spring/NetBeans API that is in 'contrib', as highlighted in my blog yesterday. That line is the thing that makes this possible at all. Without it, i.e., without being able to convert my Spring configuration file to Lookup, none of this would be possible. And what does my Spring configuration file look like? Exactly this:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="mainPanel" class="org.netbeans.nbspringdemo.MyJPanel" init-method="init">
<property name="axis">
<value>1</value>
</property>
<property name="panelComponents">
<list>
<ref bean="textField1"/>
<ref bean="textField2"/>
<ref bean="textField3"/>
<ref bean="buttonPanel"/>
</list>
</property>
</bean>
<bean id="buttonPanel" class="org.netbeans.nbspringdemo.MyJPanel" init-method="init">
<property name="axis">
<value>0</value>
</property>
<property name="panelComponents">
<list>
<ref bean="button1"/>
</list>
</property>
</bean>
<bean id="textField1" class="org.netbeans.nbspringdemo.MyJTextField" init-method="init">
<property name="text">
<value>hello 1</value>
</property>
<property name="rColor">
<value>255</value>
</property>
<property name="gColor">
<value>51</value>
</property>
<property name="bColor">
<value>102</value>
</property>
</bean>
<bean id="textField2" class="org.netbeans.nbspringdemo.MyJTextField" init-method="init">
<property name="text">
<value>hello 2</value>
</property>
<property name="rColor">
<value>0</value>
</property>
<property name="gColor">
<value>100</value>
</property>
<property name="bColor">
<value>0</value>
</property>
</bean>
<bean id="textField3" class="javax.swing.JTextField">
<property name="text">
<value>goodbye world</value>
</property>
</bean>
<bean id="button1" class="org.netbeans.nbspringdemo.MyJButton" init-method="init">
<property name="actionListener">
<ref bean="myButtonActionListener"/>
</property>
<property name="text">
<value>Click me!</value>
</property>
</bean>
<bean id="myButtonActionListener" class="org.netbeans.nbspringdemo.MyActionListener"/>
</beans>
That's the Spring configuration file that I discussed in Spring: How to Create Decoupled Swing Components on JavaLobby. The definition of the classes, i.e., the JPanel, JButton, and JTextField, are also described in that article. Read the comments to that article to see some use cases where you might want to assemble your user interface via decoupled Swing components and Spring.
Pretty cool that this is now also possible on the NetBeans Platform. I am not advocating this approach, I am merely pointing out that this is possible.
In other news. Read this blog entry about NetBeans Day Fortaleza!
Apr 22 2008, 05:49:51 AM PDT Permalink
A Picture That Speaks 1000 Words

Apr 21 2008, 08:04:12 AM PDT Permalink
The Mystery of the Hidden Folders in the Favorites Window
How is that possible..? What did I do to include the hidden folders in the Favorites window?
Answer: Tools | Options | Advanced Options | IDE Configuration | System | System Settings | Ignored Files.
Apr 20 2008, 01:49:42 PM PDT Permalink
1st Annual "Will Code For Food" Competition
NetBeans "enfant terrible" Jaroslav Tulach is writing a book on API design. It's looking pretty good and will certainly be interesting. Aside from teaching you everything you'll ever want to read about API design, it also presents an interesting history of several of the NetBeans APIs. You'll read why and how design decisions were made in NetBeans. You'll learn how to avoid the mistakes and benefit from all the wisdom of the API designers at NetBeans. Some great people are reviewing the book, so it should end up being a very good thing at the end.
Somewhere in the book, Jarda has a picture of the "Will Code HTML For Food" guy. it illustrates a point he's making in the book, about how programmers are needed everywhere, often regardless of skill. However, the publisher told him that the quality of the pic isn't very good, so yesterday Jan Chalupa took some pics of ourselves, so that we'd have a range of options to pick from, to replace the original low quality one. It was quite fun and we ended up with dozens of pics. Here are my personal favorites, one of each of the models that took part (Jaroslav Tulach, Tomas Stupka, David Simonek, and me):
The point of showing the above gallery of sad coders is that... they are here to inspire you because you can win a copy of Jarda's book, once it is released (sometime this year, probably). To win, get a pic such as the above taken of yourself. You need to look pathetic and sad. Or maybe not pathetic or sad, but edgy and toughened by life having punched you in the face. If your eyes are slightly crazed, reddened, and bulging, that's good too. Preferably you'll be displaying some weird or funny mismatch in clothing, corresponding to your supposed level of mental disarray. The background can be important too—if there's lots of graffiti in the background, for example, you could add a sense of desperation to the scene. Standing under a bridge also works. Rain would help as well. You'll need to be holding up a cardboard sign saying "Will Code HTML For Food" (or claiming you'll do something else programming-related for food). Then send it to me (geertjan DOT wielenga AT sun DOT com) and when the book is released the most pathetic picture will win a copy, signed by Jarda himself. Watch this space for further announcements and pics of early participants in this competition.
Apr 18 2008, 12:03:39 AM PDT Permalink
org.openide.filesystems.FileChangeListener
How to listen to changes to a file, via the related NetBeans FileObject, and change the DataNode's display name, tooltip, and icon as a result:
public class DemoDataNode extends DataNode implements FileChangeListener {
//The objects we are dealing with:
private static final String ICON1 = "/org/netbeans/demofiletype/demo-1.png";
private static final String ICON2 = "/org/netbeans/demofiletype/demo-2.png";
DemoDataObject obj;
Date date;
String displayName;
String tooltip;
Image icon;
int count = 0;
//Constructor:
public DemoDataNode(DemoDataObject obj) {
super(obj, Children.LEAF);
this.obj = obj;
date = new Date();
}
//Constructor for receiving the lookup:
DemoDataNode(DemoDataObject obj, Lookup lookup) {
super(obj, Children.LEAF, lookup);
//Add file change listener to the FileObject:
obj.getPrimaryFile().addFileChangeListener(this);
//Set default icon:
setIconBaseWithExtension(ICON1);
//Set default tooltip:
setShortDescription("Hello world!");
}
@Override
public String getDisplayName() {
if (null != displayName) {
return displayName;
}
return super.getDisplayName();
}
@Override
public String getShortDescription() {
if (null != tooltip) {
return tooltip;
}
return super.getShortDescription();
}
@Override
public Image getIcon(int arg0) {
if (null != icon) {
return icon;
}
return super.getIcon(arg0);
}
//When the file changes...
@Override
public void fileChanged(FileEvent arg0) {
//Increment the count:
count = count + 1;
//Depending on the modulus, switch the icon:
if (count % 2 == 1) {
icon = Utilities.loadImage(ICON1);
} else {
icon = Utilities.loadImage(ICON2);
}
//Get the milliseconds and format it:
long mills = System.currentTimeMillis();
DateFormat dateFormatter = DateFormat.getDateTimeInstance(
DateFormat.LONG,
DateFormat.LONG);
String formatted = dateFormatter.format(mills);
//Save the current display name:
String oldDisplayName = displayName;
//Save the current tooltip:
String oldShortDescription = tooltip;
//Set the new display name:
displayName = "Change " + count + " (" + formatted + ")";
//Set the new tooltip:
tooltip = formatted;
//Fire change events on the node,
//which will immediately refresh it with the new values:
fireDisplayNameChange(oldDisplayName, displayName);
fireShortDescriptionChange(oldShortDescription, tooltip);
fireIconChange();
}
@Override
public void fileFolderCreated(FileEvent arg0) {}
@Override
public void fileDataCreated(FileEvent arg0) {}
@Override
public void fileDeleted(FileEvent arg0) {}
@Override
public void fileRenamed(FileRenameEvent arg0) {}
@Override
public void fileAttributeChanged(FileAttributeEvent arg0) {}
}
When you change a document associated with the DataNode and DataObject referred to here, and you then save the document, the display name, tooltip, and icon will change.
Apr 17 2008, 12:14:38 AM PDT Permalink
Statistics: They Can't Be Serious
I'm always sceptical about statistics, on many different levels. But, principally, my issue with them is not so much in the area of gathering (although there are MANY questions to be asked there too), but in the area of interpretation. Now, I'm not the first person to suggest that interpretation renders statistics meaningless, but it's not often that the evidence is as strong as it is with the latest set of statistics I've read about. Here's the interpretation of a set of statistics, as reported in today's Guardian:
Click here to read the whole story. However, in today's Dutch Volkskrant (as reported here), commenting on the same set of statistics, the article is headlined "John McEnroe Was Usually Wrong":
The evidence here is clear. These reporters had exactly the same statistics. But they interpreted them in completely opposite directions. On top of that, one even wonders if they read the same statistics because in the Dutch report, the line judges were right in 61% of cases, while in the Guardian the line judges were wrong in 40% of them. But... that's the same thing!!! Yet... interpreted in such a way that completely different meanings are rendered. To the Guardian, the statistics support John McEnroe's objections; to the Volkskrant, the statistics undermine them.
If there's one thing that these statistics prove, it is that it pays to be multilingual and it pays to read more than one newspaper. Especially when it comes to statistics. Still, it would be nice to know whether John McEnroe was mostly right or mostly wrong. At the time, I thought he was mostly wrong, but only because I was 12 at the time and assumed that authority has special access to truth. Since then, I've learned that that's really a pretty bad assumption. How cool would it be if history were to prove that John McEnroe had been right all along? Even though he probably never thought so himself and merely enjoyed throwing tantrums on TV?
Apr 16 2008, 03:26:02 AM PDT Permalink
Step Onto the NetBeans Platform... in Japanese!
The 4-part NetBeans Platform "Selection Management Series", by Tim Boudreau, is now available in Japanese:
- http://platform.netbeans.org/tutorials/60/nbm-selection-1_ja.html
- http://platform.netbeans.org/tutorials/60/nbm-selection-2_ja.html
- http://platform.netbeans.org/tutorials/60/nbm-nodesapi2_ja.html
- http://platform.netbeans.org/tutorials/60/nbm-property-editors_ja.html
Here's the intro:
Note: The ultimate NetBeans plugin quick start is also available in Japanese:
http://platform.netbeans.org/tutorials/60/nbm-google_ja.html
Thanks very much to Masaki Katakai and the community member who worked on this translation!
Apr 15 2008, 04:03:57 AM PDT Permalink


