Download NetBeans!

20080229 Friday February 29, 2008

Getting Started Extending VisualVM (Part 4)

Update on 28 May 2008: The code and steps in this blog entry have been updated to reflect the VisualVM 1.0 APIs. If you want to download the complete sources of the sample discussed here, get the VisualVM Sample Collection from the Plugin Portal.

In part 2 of this series, I talked about extending the Overview tab in VisualVM. At the time, I didn't realize that you can also extend the Monitor tab and the Threads tab. In each case, when you initialize the plugin that provides the subtab (i.e., the tab within the Overview tab, Monitor tab, or Threads tab), you need to use either ApplicationViewsSupport.sharedInstance().getOverviewView(), ApplicationViewsSupport.sharedInstance().getMonitorView(), or ApplicationViewsSupport.sharedInstance().getThreadsView(), like this, for example:

public static void initialize() {
    ApplicationViewsSupport.sharedInstance().getOverviewView().
                registerPluginProvider(new AnagramViewPluginProvider());
}

Call the above from the ModuleInstall class and then your subtab will end up in the tab you specified (i.e., either "Overview", "Monitor", or "Threads"), as is shown here for "MonitorViewSupport", where I added a "User Interface" tab to the bottom right (the tab simply contains a screenshot of the ui of the application in question):

That's nice, because someone can create a whole separate module that does nothing other than provide a subtab:

However, what's even more interesting than that (much more interesting, in fact) is that you can make your own tabs extendable. So, while "Overview", "Monitor", and "Threads" are provided by VisualVM, you can (as explained at the start of this series) provide your own tab. The cool thing I discovered today is that YOU CAN CREATE YOUR TAB IN SUCH A WAY THAT OTHERS CAN EXTEND IT. Sorry, I never use uppercase, but here I think it is warranted. That same line of code above is rewritten to the following, in the plugin shown in the screenshot above:

public static void initialize() {
    DemoViewSupport.getInstance().getApplicationPluggableView().
            registerPluginProvider(new DemoViewPluginProvider());
}

Where does "DemoViewSupport" come from? Look in the sources of VisualVM and you will NOT find it. I created a module called "DemoPluggableView", which looks like this:

The two files you see highlighted are the two classes I needed to add in order to make the tab defined by the provider and datasource view extendable. The DemoViewSupport is defined as follows:

package org.visualvm.demopluggableview;

import com.sun.tools.visualvm.application.Application;
import com.sun.tools.visualvm.core.ui.DataSourceViewsManager;
import com.sun.tools.visualvm.core.ui.PluggableDataSourceViewProvider;

public class DemoViewSupport {

    private static DemoViewSupport instance;
    
    private DemoViewProvider DemoPluggableView = new DemoViewProvider();    

    public static synchronized DemoViewSupport getInstance() {
        if (instance == null) {
            instance = new DemoViewSupport();
        }
        return instance;
    }

    public PluggableDataSourceViewProvider getApplicationPluggableView() {
        return DemoPluggableView;
    }

    public DemoViewSupport() {
        DataSourceViewsManager.sharedInstance().addViewProvider(DemoPluggableView, Application.class);
    }
    
}

And this is the DemoPluggableView:

package org.visualvm.demopluggableview;

import com.sun.tools.visualvm.application.Application;
import com.sun.tools.visualvm.core.ui.DataSourceView;
import com.sun.tools.visualvm.core.ui.DataSourceViewProvider;
import com.sun.tools.visualvm.core.ui.DataSourceViewsManager;
import com.sun.tools.visualvm.core.ui.PluggableDataSourceViewProvider;
import java.util.Set;

public class DemoPluggableView extends PluggableDataSourceViewProvider<Application> {

    private static DataSourceViewProvider<Application> instance = new DemoPluggableView();

    @Override
    public boolean supportsViewFor(Application application) {
        //Always shown:
        return true;
    }

    @Override
    protected DataSourceView createView(Application application) {
        return new DemoDataSourceView(application);
    }

    static void initialize() {
        DataSourceViewsManager.sharedInstance().addViewProvider(instance, Application.class);
    }

    static void unregister() {
        DataSourceViewsManager.sharedInstance().removeViewProvider(instance);
    }

    @Override
    public Set<Integer> getPluggableLocations(DataSourceView arg0) {
        return ALL_LOCATIONS;
    }
    
}
Update on 28 May 2008: The code and steps in this blog entry have been updated to reflect the VisualVM 1.0 APIs. If you want to download the complete sources of the sample discussed here, get the VisualVM Sample Collection from the Plugin Portal.

Feb 29 2008, 05:48:23 PM PST Permalink

Download NetBeans!

20080228 Thursday February 28, 2008

Getting Started Extending VisualVM (Part 3)

Update on 30 May 2008: If you want to download the complete sources of the sample discussed here, get the VisualVM Sample Collection from the Plugin Portal.

In response to the question from Klaus at the end of my blog entry yesterday—yes, you can add subnodes to the application type node in VisualVM. Below you see two examples. The upper part shows a set of subnodes created by the GlassFish plugin for VisualVM, which you can get from visualvm.dev.java.net, together with the sources of the whole of VisualVM. You can see subnodes and that those nodes also have subnodes. Today I used that GlassFish sample as a kind of template for my own experiments. As you can see, now the Anagram Game application type node (in the lower part of the screenshot) has subnodes, for the attributes in the application, together with their type. And you can see, I've opened the MBeans tab in the main part of the tool (that MBeans tab is also provided by a plugin which you can get from the Plugin Manager in VisualVM), which I opened here so you can see that the attributes in the MBeans tab match those in my application type subnodes. (By the way, can someone remind me about how to get the values from an MBeanAttributeInfo?) Here's the result:

The amount of coding needed for subnodes is not insignificant; on the other hand, the GlassFish plugin (even though it is a prototype at the moment, so caveat emptor) is very helpful because it provides almost all the code you need. The only parts I needed to provide myself were the pieces relating to JMX. Here's a pic showing all the classes that I added in addition to those I had already created (i.e., yesterday for the sample that I discussed at that point in my blog):

Hope this serves to inspire you of the potential that VisualVM has for your own applications. Most entry points into VisualVM have now been discussed here in this blog, but a few still remain, and there are several t's to cross and i's to dot. So watch this space for further learnings in connection to VisualVM.

Update on 30 May 2008: If you want to download the complete sources of the sample discussed here, get the VisualVM Sample Collection from the Plugin Portal.

Feb 28 2008, 02:20:28 PM PST Permalink

Download NetBeans!

20080227 Wednesday February 27, 2008

Getting Started Extending VisualVM (Part 2)

Update on 30 May 2008: The code and steps in this blog entry have been updated to reflect the VisualVM 1.0 APIs. If you want to download the complete sources of the sample discussed here, get the VisualVM Sample Collection from the Plugin Portal.

In Getting Started Extending VisualVM (Part 1), yesterday, we used a DataSourceViewProvider to create a new DataSourceView. The latter was available to all types of applications shown in VisualVM. Today we'll create a new application type and... provide some functionality specifically for that type. First, we'll provide a new tab in the Overview section and we'll also provide a menu item, specifically for our application type.

Only those applications are recognized for which specific support has been provided. Otherwise, a default icon is shown and the default tabs are available. Let me stress at this point that for most applications, the default tabs are enough. See VisualVM: Free and Open Source Java Troubleshooter, to see what the default VisualVM provides. However, for GlassFish, or any other server, you probably want some subnodes for deployed applications. So, in scenarios where you have an application that has special needs, or for which you want to display specific information, you can (if you want to) create a new VisualVM "application type".

For this example, we will create a new application type for the Anagram Game that is bundled with the IDE. Typically, an application type is identified by its main class, which in the case of the Anagram Game is "com.toy.anagrams.ui.Anagrams". At the end, you'll have a new icon for the Anagram Game, with a new Overview tab (showing, for the sake of this simple example, a screenshot of the application), and a new "Show Anagram PID" menu item on the Anagram Game application type node, as shown in the illustration below:

The steps to take for these three entry points (i.e., application type, overview tab, and menu item), are as follows:

  1. Initialize the Entrypoints. In the module install class, as shown yesterday, initialize the three entry point implementations that you are about to create:

    package org.visualvm.demoapplicationtype;
    
    import org.openide.modules.ModuleInstall;
    
    public class Installer extends ModuleInstall {
    
        private static AnagramApplicationTypeProvider INSTANCE = new AnagramApplicationTypeProvider();
    
        @Override
        public void restored() {
            ApplicationTypeFactory.getDefault().registerProvider(INSTANCE);
        }
    
        @Override
        public void uninstalled() {
            ApplicationTypeFactory.getDefault().unregisterProvider(INSTANCE);
        }
        
    }

  2. Define the application type. Use the VisualVM Application Type Template for this purpose. As stated, we use the Anagram Game main class to determine that we want an application type to be created:

    import com.sun.tools.visualvm.application.Application;
    import com.sun.tools.visualvm.application.jvm.Jvm;
    import com.sun.tools.visualvm.application.type.ApplicationType;
    import com.sun.tools.visualvm.application.type.MainClassApplicationTypeFactory;
    
    public class AnagramApplicationTypeProvider extends MainClassApplicationTypeFactory {
    
        @Override
        public ApplicationType createApplicationTypeFor(Application app, Jvm jvm, String mainClass) {
    
            //TODO: Specify the name of the application's main class here:
            if ("com.toy.anagrams.ui.Anagrams".equals(mainClass)) {
                return new AnagramApplicationType(app.getPid());
            }
            return null;
    
        }
    
    }

    And here is the definition of the application type itself:

    import com.sun.tools.visualvm.application.type.ApplicationType;
    import java.awt.Image;
    import org.openide.util.Utilities;
    
    public class AnagramApplicationType extends ApplicationType {
    
        protected final int appPID;
    
        public AnagramApplicationType(int pid) {
            appPID = pid;
        }
    
        @Override
        public String getName() {
            return "Anagram";
        }
    
        @Override
        public String getVersion() {
            return "1.0";
        }
    
        @Override
        public String getDescription() {
            return "Application type for Anagram";
        }
    
        @Override
        public Image getIcon() {
            return Utilities.loadImage("com/sun/tools/visualvm/core/ui/resources/snapshot.png", true);
        }
    
    }

  3. Define the Overview Window Extension. Here we check that we're dealing with our application type. If so, the Overview extension is created:

    import com.sun.tools.visualvm.application.Application;
    import com.sun.tools.visualvm.application.type.ApplicationTypeFactory;
    import com.sun.tools.visualvm.application.views.ApplicationViewsSupport;
    import com.sun.tools.visualvm.core.ui.DataSourceViewPlugin;
    import com.sun.tools.visualvm.core.ui.DataSourceViewPluginProvider;
    
    class AnagramViewPluginProvider extends DataSourceViewPluginProvider<Application> {
    
        protected DataSourceViewPlugin createPlugin(Application application) {
            return new AnagramOverview(application);
        }
    
        protected boolean supportsPluginFor(Application application) {
            if (ApplicationTypeFactory.getApplicationTypeFor(application) instanceof AnagramApplicationType) {
                return true;
            }
            return false;
        }
    
        static void initialize() {
            ApplicationViewsSupport.sharedInstance().getOverviewView().
                    registerPluginProvider(new AnagramViewPluginProvider());
        }
    
        static void uninitialize() {
            ApplicationViewsSupport.sharedInstance().getMonitorView().
                    unregisterPluginProvider(new AnagramViewPluginProvider());
        }
       
    }

    And here is our new tab in the Overview window:

    import com.sun.tools.visualvm.application.Application;
    import com.sun.tools.visualvm.core.ui.DataSourceViewPlugin;
    import com.sun.tools.visualvm.core.ui.components.DataViewComponent;
    import com.sun.tools.visualvm.core.ui.components.ScrollableContainer;
    import javax.swing.JPanel;
    
    public class AnagramOverview extends DataSourceViewPlugin {
    
        AnagramOverview(Application application) {
            super(application);
        }
    
        public DataViewComponent.DetailsView createView(int location) {
            switch (location) {
                case DataViewComponent.TOP_RIGHT:
                    JPanel panel = new JPanel();
                    return new DataViewComponent.DetailsView("User Interface", null, 30,
                            new ScrollableContainer(panel), null);
                default:
                    return null;
            }
        }
    
    }

    Now we need to initialize our new tab, using the lines in bold below:

    public class Installer extends ModuleInstall {
    
        private static AnagramApplicationTypeFactory INSTANCE = new AnagramApplicationTypeFactory();
    
        @Override
        public void restored() {
            ApplicationTypeFactory.getDefault().registerProvider(INSTANCE);
            AnagramViewPluginProvider.initialize();
        }
    
        @Override
        public void uninstalled() {
            ApplicationTypeFactory.getDefault().unregisterProvider(INSTANCE);
            AnagramViewPluginProvider.uninitialize();
        }
    
    }

  4. Define the Menu Item. Use the VisualVM Action Template for this purpose. Then tweak it, if necessary, as shown below. Here, again we check for our application type and then create the menu item:

    import com.sun.tools.visualvm.application.Application;
    import com.sun.tools.visualvm.application.type.ApplicationTypeFactory;
    import com.sun.tools.visualvm.core.ui.actions.SingleDataSourceAction;
    import java.awt.event.ActionEvent;
    import javax.swing.Action;
    import javax.swing.JOptionPane;
    
    public class AnagramAction extends SingleDataSourceAction<Application> {
    
        public AnagramAction() {
            super(Application.class);
            putValue(Action.NAME, "Show Anagram PID");
            putValue(Action.SHORT_DESCRIPTION, "Demoes a menu item");
        }
    
        @Override
        protected void actionPerformed(Application application, ActionEvent arg1) {
            JOptionPane.showMessageDialog(null, application.getPid());
        }
    
        //Here you can determine whether the menu item is enabled,
        //depending on the data source type that is selected. In this
        //example, the menu item is enabled for all types within
        //the current data source:
        @Override
        protected boolean isEnabled(Application application) {
            if (ApplicationTypeFactory.getApplicationTypeFor(application) instanceof AnagramApplicationType) {
                return true;
            }
            return false;
        }
    
    }

    And here is the registration in the layer.xml file, which initializes our action as a menu item:

        <folder name="VisualVM">
            <folder name="ExplorerPopupSelection">
                <file name="org-visualvm-demoapplicationtype-AnagramAction.instance">
                    <attr name="SystemFileSystem.localizingBundle" stringvalue="org.visualvm.demoapplicationtype.Bundle"/>
                    <attr name="position" intvalue="3000"/>
                </file>
            </folder>
        </folder>

This should give you enough of a start to create your own application types with some functionality specific to the type in question.

Update on 30 May 2008: The code and steps in this blog entry have been updated to reflect the VisualVM 1.0 APIs. If you want to download the complete sources of the sample discussed here, get the VisualVM Sample Collection from the Plugin Portal.

Feb 27 2008, 06:07:34 AM PST Permalink

Download NetBeans!

20080226 Tuesday February 26, 2008

Getting Started Extending VisualVM (Part 1)

Update on 28 May 2008: The code and steps in this blog entry have been updated to reflect the VisualVM 1.0 APIs. If you want to download the complete sources of the sample discussed here, get the VisualVM Sample Collection from the Plugin Portal.

VisualVM, i.e., the bundling of the JDK's troubleshooting tools, is perfectly positioned to become the Java developer's troubleshooter of choice—free, open source, active mailing list, with a bunch of great programmers working on it. On top of all that, it is pluggable. Do I need to explain why that makes it even more viable? Probably not. But, imagine if it wasn't—there'd be no way to extend it independently of the rest of the code. Already, that is, without the APIs being 100% figured out, there's an external plugin available, that is, the plugin was not made by one of the core VisualVM developers: Thread Dump Analyzer for VisualVM, by Ingo Rockel. In this blog entry, I will look at how you can get started extending VisualVM yourself.

In theory, you can use any editor or IDE of your choice to extend VisualVM. In reality, since VisualVM is built on the NetBeans Platform, and since NetBeans IDE is currently the only IDE that has tools (i.e., wizards, code generators, etc) for that platform, NetBeans IDE is the IDE of choice when extending VisualVM. (And that fact, to a very small extent, explains my recent interest in IntelliJ. VisualVM should be extendable in Eclipse, IntelliJ, JDeveloper, JBuilder, and any other IDE. The "only" things needed there are tools for generating a project structure, a few wizards, and some interconnections with the layer file.)

Take the following steps to get going extending VisualVM:

  1. Get the Stuff You Will Need. The binary, the sources, and the Javadoc are all open sourced and freely available to you. Since you're creating a plugin, you're by definition an adventurous type of programmer. Hence, the more info you have the better and hence you will want to look at sources and Javadoc and anything else you can get your hands on. All of that's available thanks to the open sourcing of VisualVM.

  2. Register the VisualVM Binary in NetBeans IDE. When you put the binary in the NetBeans Platform Manager, you will be able to compile your plugin against that binary. The VisualVM's APIs are then available to your plugin. Here's me registering the VisualVM binary:

    Also, make very sure that you have JDK 6 registered in the IDE, because VisualVM will not work with any earlier version of the JDK. So, the plugin that you will create must use some version of JDK 6. Also, note that some of the tabs in VisualVM will not be visible for applications that are not running on JDK 6.

  3. Open the VisualVM Sources in the NetBeans IDE Favorites Window. Put the sources in the Favorites window so that... you can explore the "plugins" folder, which you will want to do to learn how the existing three plugins ("glassfish", "jconsole", "mbeans") are created:

    By the way, you could also open them in the Projects window, since they're all in a NetBeans module, as you can see above from the fact that there's a 'nbproject' folder.

  4. Examine the VisualVM Entrypoint Document. When you download the "API Docs" from https://visualvm.dev.java.net/, you will find, apart from the Javadoc, a very handy overview of all the places where your plugin can enrich VisualVM:

    The above screenshot doesn't do the document justice. You really need to see it live, while realizing that there is still some movement in terms of the API itself and hence the document itself may also change.

  5. Say "Hello World" to VisualVM. You now have all your resources and the time has come to make your first trip into the heart of VisualVM. If you're not familiar with how plugins for the NetBeans Platform work, you're in luck, because there are many wonderful things that you can learn about, here, in the "NetBeans Modules and Rich-Client Applications Learning Trail". Assuming that you are at least familiar with the NetBeans Module Quick Start, let's say "Hello World" to VisualVM.

    Now, we'll say "Hello World". We will create a new tab that looks as follows:

    There will be no data in our tab, we'll simply create it and add some placeholders for content. Next time we'll add some interesting data.

    Let's get started! Create a new NetBeans module and make sure to specify that you want to build against the VisualVM binary that you registered in step 2 above:

    Use the "Module Installer" wizard, which will create a class that is called when the plugin is installed into VisualVM. (And, now that I know a little bit about IntelliJ, I can inform you that IntelliJ's "ApplicationComponent" class is comparable to the NetBeans "ModuleInstall" class, which is the class that the Module Installer wizard creates.) Here's where you'll find the wizard in the New File dialog:

    Once you've completed the wizard, implement the ModuleInstall.restored method and the ModuleInstall.uninstalled methods as follows:

    @Override
    public void restored() {
        HelloWorldViewProvider.initialize();
    }
    
    @Override
    public void uninstalled() {
        HelloWorldViewProvider.unregister();
    }

    Now add a dependency (which you should know how to do after reading the NetBeans Module Quick Start) on "VisualVM-Application" and "VisualVM-Core", which provide the applicable APIs into the VisualVM. Create a class called "HelloViewProvider", as follows:

    public class HelloWorldViewProvider extends DataSourceViewProvider<Application> {
    
        private static DataSourceViewProvider instance = new HelloWorldViewProvider();
    
        @Override
        public boolean supportsViewFor(Application application) {
            //Always shown:
            return true;
        }
    
        @Override
        protected DataSourceView createView(Application application) {
            return new HelloWorldView(application);
        }
    
        static void initialize() {
            DataSourceViewsManager.sharedInstance().addViewProvider(instance, Application.class);
        }
    
        static void unregister() {
            DataSourceViewsManager.sharedInstance().removeViewProvider(instance);
        }
        
    }

    By reading the Javadoc that you downloaded earlier, you'll know what the two overridden methods above are all about:

    You should be using the following import statements at this point:

    import com.sun.tools.visualvm.application.Application;
    import com.sun.tools.visualvm.core.ui.DataSourceView;
    import com.sun.tools.visualvm.core.ui.DataSourceViewProvider;
    import com.sun.tools.visualvm.core.ui.DataSourceViewsManager;

    Finally, we'll create our HelloView class, which is created by our provider above:

    public class HelloWorldView extends DataSourceView {
    
        private DataViewComponent dvc;
        //Make sure there is an image at this location in your project:
        private static final String IMAGE_PATH = "org/hellovisualvm/coredump.png"; // NOI18N
    
        public HelloWorldView(Application Application) {
            super(Application,"Hello World", new ImageIcon(Utilities.loadImage(IMAGE_PATH, true)).getImage(), 60, false);
        }
    
        protected DataViewComponent createComponent() {
    
            //Data area for master view:
            JEditorPane generalDataArea = new JEditorPane();
            generalDataArea.setBorder(BorderFactory.createEmptyBorder(14, 8, 14, 8));
    
            //Panel, which we'll reuse in all four of our detail views for this sample:
            JPanel panel = new JPanel();
    
            //Master view:
            DataViewComponent.MasterView masterView = new DataViewComponent.MasterView("Hello World Overview", null, generalDataArea);
    
            //Configuration of master view:
            DataViewComponent.MasterViewConfiguration masterConfiguration =
                    new DataViewComponent.MasterViewConfiguration(false);
    
            //Add the master view and configuration view to the component:
            dvc = new DataViewComponent(masterView, masterConfiguration);
    
            //Add detail views to the component:
            dvc.addDetailsView(new DataViewComponent.DetailsView(
                    "Hello World Details 1", null, 10, panel, null), DataViewComponent.TOP_LEFT);
            dvc.addDetailsView(new DataViewComponent.DetailsView(
                    "Hello World Details 2", null, 20, panel, null), DataViewComponent.TOP_RIGHT);
            dvc.addDetailsView(new DataViewComponent.DetailsView(
                    "Hello World Details 3", null, 30, panel, null), DataViewComponent.BOTTOM_RIGHT);
            dvc.addDetailsView(new DataViewComponent.DetailsView(
                    "Hello World Details 4", null, 40, panel, null), DataViewComponent.BOTTOM_RIGHT);
    
            return dvc;
    
        }
    
    }

    Here are the import statements that you should now have:

    import com.sun.tools.visualvm.application.Application;
    import com.sun.tools.visualvm.core.ui.DataSourceView;
    import com.sun.tools.visualvm.core.ui.components.DataViewComponent;
    import javax.swing.BorderFactory;
    import javax.swing.ImageIcon;
    import javax.swing.JEditorPane;
    import javax.swing.JPanel;
    import org.openide.util.Utilities;

  6. Install the Plugin. Install the plugin and you'll see this new tab in VisualVM:

Hurray, you've said "Hello World" and are now on the road to integrating your own cool tools into VisualVM! Next time we'll add some data.

Update on 28 May 2008: The code and steps in this blog entry have been updated to reflect the VisualVM 1.0 APIs. If you want to download the complete sources of the sample discussed here, get the VisualVM Sample Collection from the Plugin Portal.

Feb 26 2008, 06:25:14 AM PST Permalink

Download NetBeans!

20080225 Monday February 25, 2008

IntelliJ is Fun!

I've been getting acquainted with plugin development in IntelliJ. My knowledge of IntelliJ is a day or two old and already I've got something useful integrated, my good old friend the YouTube Data API:

Once installed, you type a search string in the Settings dialog and then invoke a new menu item under the Window menu. Then the list that you see above is populated with movies from YouTube with titles that match the search string. So, for the above, my search string is "Bill Clinton". Whenever you select a new item in the list, a small thumbnail that relates to the selected movie is shown to the right of the list and the browser opens displaying the movie.

My adventures in the land of IntelliJ are described here on JetBrains Zone:

I also contributed the result (but without the icon that appears to the right of the list, because I created that part later) to the IntelliJ Plugin Repository, under the name "Search YouTube Plugin".

I've learned a lot and, since I only have an evaluation license, I better continue learning a lot because my time will soon be up.

Feb 25 2008, 10:38:49 AM PST Permalink

Download NetBeans!

20080223 Saturday February 23, 2008

The 5 Stages of Google-Based Troubleshooting

I recently reported on getting a new laptop. I also mentioned I was now on Ubuntu 7.10 and that the world looked good. Well, it's now 10 days later and I've encountered my first BIG gotcha. To sum it up: I found that I was unable to watch DVDs. Some weird message appeared, about 'libdvdcss', among other things. I've now solved the problem, completely, as far as I can tell. And solving the problem was the same as every other time I've solved similar problems.

The typical scenario is this—you've got some new software and, at some stage, either early or later, some essential thing doesn't work. Either it never worked in the first place or it suddenly stops working. The only thing to help you is an obscure message, such as the one I got about 'libdvdcss'. Then you start solving the problem, by using Google. In the end, you do solve it, even though there are many moments when you think you never will, even though it sometimes takes days of frustration. However, in each case, I've found that the process goes more or less as follows, here I'll use this specific problem as an example and hope it will help others (when they're Googling too):

  • Stage 1: Innocence. The early stage, where you think: "Oh. A strange error message. That shouldn't be hard to solve. Seems innocuous enough. After all, I'm not the first person who's wanted to watch DVDs on Ubuntu, so there's got to be a solution." So you go to Google and start looking around, calmly, your mind still tranquil and unfevered. You are like a freshfaced sailor setting off on a horrific journey, filled with seamonsters and pounding waves, yet all of that lies before you, and all you can see are the blue waves and seagulls.

  • Stage 2: False Hope. Quickly you find your first tip, such as How To: Install libdvdcss on Ubuntu Gutsy. The entire solution in one small blog entry! Oh, happy day! If you could jump in the air with your hands clasped and your heels clicking, you would do so, except that you immediately set about implementing the tip. All the steps that you're told to follow fly by, one by one, without a hitch. You have not the tiniest understanding of what you've just done, but who cares? You simply want to watch DVDs, not understand the insides of Linux and all its libs, so you complete all the little steps the tip gives you, fire up your movie player and... you get the same error message! Dang!

  • Stage 3: Confusion. You Google a little bit more, but get similar advice. Then you are told about the various ways you can implement the SAME advice. (Seriously, this one is pretty good.) You think: "Hmmm. Maybe it will make a difference if I take the same steps, but this time using the Synaptic Package Manager." You try that approach for a while. Oddly, at random moments, you seem to get completely DIFFERENT error messages. You Google those too. In the process, you've downloaded (via Synaptic Package Manager, via apt-get install, via Add/Remove) a whole host of libs and applications. Your list of movie players is now as long as your arm, on the basis that "if it doesn't work with Xine, maybe it will work with VLC". Then you Google some more and find someone praising gxine or MPlayer and so, each time with a tiny flicker of hope, you install those too. You're not much nearer to your goal (or, maybe you are, who knows) but you now have the best collection of movie players imaginable:

    Pity you can't watch movies though.

  • Stage 4: The overly-aggressive APM. There's always a very significant "deep breath" moment. That's where you say to yourself: "OK. Let's back up a moment. This simply can't be this hard." At that point, you try the product's official site. That's when I read about the Restricted Formats situation on Ubuntu, in their own Ubuntu docs. But they also have another one, called Restricted Formats/Playing DVDs. So then you realize that Ubuntu's docs are layers within layers, somehow. But, you're getting further, you think. And then, suddenly, the error message changes again. Was it because of the restart? Because of the "regionset" thingummy that an anonymous random thread advised you about and that you clung to like an old inebriated lady to her last bottle of aging whisky? Who knows? And what does the message tell you? Initially something about "NAV". Later, something about "APM"... and then you discover that this APM, who you've only just been introduced to... might be overly aggressive. Aaaaargh. What does it all mean? In which lower crevice of hell have you been flung?! Is there no end to the pain?

  • Stage 5: The Sudden Fluke! Either because you made the APM less aggressive or because you suddenly started firing up your movie player with the "sudo" command (or for one of a host of other things), the movie suddenly starts playing! But you have no idea why. Carefully, trying not to breathe lest the gods of flukedom crap upon your head, you try and figure out what happened. Which of the many random ingredients thrown haphazardly into the stew turned your sick brew into a Coca-Cola? Perhaps you'll never know! Quick, watch a movie now that you can, because it may be your last! Pick whatever is currently in your DVD drive because, possibly, by opening the DVD drive you might end up angering the gods. So then I watched "Red Dragon", highly recommended by the way, especially after all of the above:

And, at the climax (or one of the climaxes since this movie has so many), I figured it out (or I believe I did and by now I'm sure of it): for some reason (my fault? Ubuntu's? a random something else?) when I installed the movie players, I didn't have permission to use them, because I installed as root. OK, that doesn't make sense. But, if I don't set 'sudo chmod 777' on each of the movie player's executables, I get an error message, sometimes about region settings and sometimes about 'libdvdcss'. I'm sure that several of the steps I took above were necessary, but like many others out there in Googleland, they just weren't enough. You need to set permissions on the installed movie players, otherwise you will not be able to watch DVDs in them. That's just the way it is, unfortunately.

And, well, anyway, the above 5 stages are just typical of this kind of process. It's always a small fluke somewhere that makes it all work. (There's also a 6th stage, by the way, one which I'm not looking forward to, called "The Regression".) And I'm not blaming anyone (not Ubuntu, not movie players, not regional settings, and not Anthony Hopkins, who is as likely to be at fault as anyone else). I guess that's just the way it is in today's world. Travel with Google and you'll always arrive where you wanted to go, but the route there is always a scenic one.

Feb 23 2008, 05:34:59 AM PST Permalink

Download NetBeans!

20080222 Friday February 22, 2008

Domain Objects Should Not Be Unknown to NetBeans IDE

When you use the "Add to Palette" action in NetBeans IDE, because you want to make your domain objects reusable, the new items look like this in the palette:

Not very nice, because that's the icon for unknown palette items. Somehow you should be able to at least assign icons to the item, as well as, preferably, a display name and tooltip. I looked at the XML file that is generated by the IDE when adding a new item to the palette. That XML file does not contain an attribute for icons, hence the unknown icon is shown. I hardcoded the references to the icons directly into the XML file (using the info found in the NetBeans Code Snippet Module Tutorial), restarted the IDE, and now the items for my domain objects look as follows, which is much better:

I will look in Issuezilla and, if there is no issue for this, I will create one. Either the "unknown icon" should be nicer (simplest solution) or, ideally, the user should be able to select icons for their domain object palette items.

These were the two tags I added, reused from the "Choose Bean" item:

   <icon16 urlvalue="nbres:/org/netbeans/modules/form/resources/palette/choose_bean_16.png" />
   <icon32 urlvalue="nbres:/org/netbeans/modules/form/resources/palette/choose_bean_32.png" />

Feb 22 2008, 09:05:21 AM PST Permalink

Download NetBeans!

20080221 Thursday February 21, 2008

Applet of the Week!

I've been in touch with NetBeans user Corey Hunt over the last few days who was working on creating an applet that would make use of the NetBeans Visual Library. At first I didn't think it would be possible at all, but David Kaspar, the library's creator, assured me that it was. There were several problems to solve first, though. Some general guidelines follow:

  1. Work through the applet tutorial to understand how applets are constructed in NetBeans IDE.

  2. Next, add the Visual Library JAR and the Utilities JAR to your Java application and then integrate the Visual Library code into the applet. For example, this is Corey's code that does the job:

    public class AppletRes extends JApplet {
    
        private DemoGraphScene scene = new DemoGraphScene();
        private javax.swing.JScrollPane jScrollPane1;
    
        /** Initializes the applet AppletRes */
        @Override
        public void init() {
            try {
                java.awt.EventQueue.invokeAndWait(new Runnable() {
    
                    public void run() {
                        initComponents();
                        jScrollPane1 = new javax.swing.JScrollPane();
                        jScrollPane1.setViewportView(scene.createView());
                        add(jScrollPane1, java.awt.BorderLayout.CENTER);
    
    
                    }
                });
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
        ....
        ....
        ....
    

  3. Then, once you've got your applet working, before attaching it to the web application (which is explained in the tutorial above), you need to make sure that the content of the two JARs (i.e., the Visual Library JAR and the Utilities JAR) are wrapped into the JAR that contains the applet. Here's the Ant target, something like this is needed in your build.xml file:

    <target name="-post-jar">
        <jar update="true" destfile="${dist.jar}">
            <zipfileset src="${libs.swing-layout.classpath}"/>
            <zipfileset src="${file.reference.org-netbeans-api-visual.jar-1}"/>
            <zipfileset src="${file.reference.org-openide-util.jar-1}"/>
        </jar>
    </target>

  4. At this point you have your JAR containing your applet, plus the content of the three JARs above. Now that JAR can be added to the web application, but it also needs to be signed. Follow the steps described here, which pretty much describes everything, and is a blog entry I'd forgotten having ever written, actually, until Corey pointed me back to it.

And that's about it! Corey sent me a link to the result. Below you see his face (yes that's Corey), which can be moved around, it can be zoomed in/out, and the label below the pic can be edited. And everything you see below is in an applet:

It is the first time that I've seen the Visual Library API alive and kicking on the web. In his final note on the above, Corey writes: "Like I said, not much, but it represents a lot of gained knowledge and I am pretty stoked. Now, I am off to add functionality." Here's hoping he'll tell me when he gets to the next stage so I can share it here!

Feb 21 2008, 07:43:15 AM PST Permalink

Download NetBeans!

20080220 Wednesday February 20, 2008

Fixable Hint

Yesterday's hint showed up whenever System.out.println or JOptionPane.showMessageDialog are used in the code. Today, let's enhance the hint so that it turns into a lightbulb that the user can click. When clicked, the offending line of code will simply be removed:

Here's the same class as yesterday, supplemented with an implementation of the EnhancedFix class, which is the third argument of our ErrorDescription. The code below produces the result shown above; when the hint is clicked, the line is removed.

public class WrongDebugMethodologies extends AbstractHint {

    //private static final List<Fix> NO_FIXES = Collections.<Fix>emptyList();
    
    private static final Set<Tree.Kind> TREE_KINDS =
            EnumSet.<Tree.Kind>of(Tree.Kind.METHOD_INVOCATION);

    public WrongDebugMethodologies() {
        super(true, true, AbstractHint.HintSeverity.WARNING);
    }

    public Set<Kind> getTreeKinds() {
        return TREE_KINDS;
    }

    public List<ErrorDescription> run(CompilationInfo info, TreePath treePath) {
        
        Tree t = treePath.getLeaf();

        Element el = info.getTrees().getElement(treePath);
        String name = el.getSimpleName().toString();

        if (name.equals("showMessageDialog") || name.equals("println")) {

            JTextComponent editor = EditorRegistry.lastFocusedComponent();
            Document doc = editor.getDocument();

            SourcePositions sp = info.getTrees().getSourcePositions();
            int start = (int) sp.getStartPosition(info.getCompilationUnit(), t);
            int end = (int) sp.getEndPosition(info.getCompilationUnit(), t);
            String bodyText = info.getText().substring(start, end);

            List<Fix> fixes = new ArrayList<Fix>();
            fixes.add(new MessagesFix(doc, start, bodyText));

            return Collections.<ErrorDescription>singletonList(
                    ErrorDescriptionFactory.createErrorDescription(
                    getSeverity().toEditorSeverity(),
                    getDisplayName(),
                    fixes,//NO_FIXES,
                    info.getFileObject(),
                    start,
                    end));
        }
        return null;
    }

    public void cancel() {
    // Does nothing
    }

    public String getId() {
        return "Wrong_Debug"; // NOI18N
    }

    public String getDisplayName() {
        return NbBundle.getMessage(WrongDebugMethodologies.class, "LBL_Debug");
    }

    public String getDescription() {
        return NbBundle.getMessage(WrongDebugMethodologies.class, "DSC_Debug");
    }

    class MessagesFix implements EnhancedFix {

        Document doc = null;
        int start = 0;
        String bodyText = null;

        public MessagesFix(Document doc, int start, String bodyText) {
            this.doc = doc;
            this.start = start;
            this.bodyText = bodyText;
        }

        public CharSequence getSortText() {
            return "charsequence";
        }

        public String getText() {
            return "Let's remove it...";
        }

        public ChangeInfo implement() throws Exception {
            //Add 1 character, for the semi-colon:
            doc.remove(start, bodyText.length() + 1);
            //Display message to user in status bar:
            StatusDisplayer.getDefault().setStatusText("Removed: " + bodyText);
            return null;
        }
    }
    
}

Today on NetBeans Zone. A short run through of how to bind a JTable to Swing controls in NetBeans IDE.

Feb 20 2008, 01:30:42 AM PST Permalink

Download NetBeans!

20080219 Tuesday February 19, 2008

Hey buddy, you probably want to remove that JOptionPane...

I have a bad tendency to use JOptionPanes and System.out for debugging, rather than the IDE's state of the art Debugger. What's almost as bad is that I tend to leave those method calls in my code. But now I've solved my problem. I created a hint in the NetBeans editor that tells me about all the instances where I'm using these 'antipatterns':

Helpful marks appear in the right side of the editor, so that I can see everywhere in my code where these method calls are found. As the user of my own hint, I can change the way it is displayed, i.e., either as a warning (as above) or as a squiggly red error mark. I didn't need to do any coding for that part of my hint. By registering it in the layer, it landed on its own two feet in the Options window for these purposes:

To create hints yourself, similar to the above, you need a reasonable understanding of the new 6.0 Java Language Infrastructure. The NetBeans Java Language Infrastructure Tutorial will get you up and running, after which you should read the Java Infrastructure Developer's Guide and the Retouche Developer FAQ.

Armed with all that knowledge, you are ready to get stuck into the hint infrastructure. To get you warmed up, here's how I created the above hint. First, register it in the layer file:

<folder name="org-netbeans-modules-java-hints">

    <folder name="rules">

        <folder name="hints">
            <folder name="general">
                <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.demohint.Bundle"/>
                <file name="org-netbeans-modules-demohint-WrongDebugMethodologies.instance"/>
            </folder> 
        </folder> 

    </folder> 

</folder>

Then create the class referred to above, WrongDebugMethodologies.java, with this content:

public class WrongDebugMethodologies extends AbstractHint {

    private static final List<Fix> NO_FIXES = Collections.<Fix>emptyList();
    
    private static final Set<Tree.Kind> TREE_KINDS =
            EnumSet.<Tree.Kind>of(Tree.Kind.METHOD_INVOCATION);

    public WrongDebugMethodologies() {
        super(true, true, AbstractHint.HintSeverity.WARNING);
    }

    public Set<Kind> getTreeKinds() {

        return TREE_KINDS;
    }

    public List<ErrorDescription> run(CompilationInfo info, TreePath treePath) {

        Tree t = treePath.getLeaf();

        Element el = info.getTrees().getElement(treePath);
        String name = el.getSimpleName().toString();
        
        if (name.equals("showMessageDialog")) {
            return Collections.<ErrorDescription>singletonList(
                    ErrorDescriptionFactory.createErrorDescription(
                    getSeverity().toEditorSeverity(),
                    getDisplayName(),
                    NO_FIXES,
                    info.getFileObject(),
                    (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), t),
                    (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), t)));

        }

        return null;
    }

    public void cancel() {
    // Does nothing
    }

    public String getId() {
        return "Wrong_Debug"; // NOI18N
    }

    public String getDisplayName() {
        return NbBundle.getMessage(WrongDebugMethodologies.class, "LBL_WrongDebug");
    }

    public String getDescription() {
        return NbBundle.getMessage(WrongDebugMethodologies.class, "DSC_WrongDebug");
    }

}

Finally, add the following strings to your bundle:

LBL_WrongDebug=Hey buddy, you probably want to remove that JOptionPane...
DSC_WrongDebug=JOptionPane shouldn't be used, probably.
org-netbeans-modules-java-hints/rules/hints/general=General

Required modules: Editor Hints (Experimental), File System API, Javac API Wrapper, Java Hints, Java Source, Utilities API. As you can see at least one of these is experimental, requiring the implementation version to be used.

Today on NetBeans Zone. Antonio Santiago tells us about Changing a Default Action's Icon in NetBeans RCP.

Feb 19 2008, 06:44:47 AM PST Permalink

Download NetBeans!

20080218 Monday February 18, 2008

Utility Method for Retrieving Current Hints

I believe it would be really cool if we had a Hints wizard, which would generate the basic stubs for new hints for NetBeans IDE. Imagine if we had that—wouldn't that increase the chance (a lot) of NetBeans IDE getting hint contributions from the broader community? And getting more hints is one significant step to NetBeans IDE's editor (especially its Java editor) becoming an even more valuable tool to its users.

One part of such a wizard would let the hint developer register the hint in the layer file via the wizard, i.e., without actually needing to touch the layer file itself. So, here's a utility method for retrieving the currently registered hints, which would probably end up in some kind of list in the wizard. For testing purposes, I've put the code into a CallableSystemAction.performAction, which results in this:

Here's the code itself:

public void performAction() {
    try {

        //Prepare Output window:
        OutputWriter writer;
        InputOutput io = IOProvider.getDefault().getIO("Hint Folders", false);
        writer = io.getOut();
        writer.reset();
        io.select();

        //Get Hints folder:
        FileObject hintObject = Repository.getDefault().getDefaultFileSystem().getRoot().
                getFileObject("org-netbeans-modules-java-hints/rules/hints");
        DataFolder hintFolder = DataFolder.findFolder(hintObject);

        //Work with children of Hints folder:
        DataObject[] hintFolderKids = hintFolder.getChildren();
        for (int i = 0; i < hintFolderKids.length; i++) {
            DataFolder oneHintFolderKid = (DataFolder) hintFolderKids[i];

            //Work with grandchildren of Hints folder:
            DataObject[] oneHintFolderGrandKids = oneHintFolderKid.getChildren();
            StringBuilder builder = new StringBuilder();
            for (int j = 0; j < oneHintFolderGrandKids.length; j++) {
                DataObject oneHintFilderGrandKid = oneHintFolderGrandKids[j];
                builder.append("-- " + oneHintFilderGrandKid.getNodeDelegate().getDisplayName() + "\n");
            }

            //Create message, consisting of one child, plus all related grandchildren:
            String msg = "\n" + oneHintFolderKid.getNodeDelegate().getDisplayName() + "\n" + builder;

            //Write to Output window:
            writer.println(msg);

        }
    } catch (IOException ex) {
        Exceptions.printStackTrace(ex);
    }

}

Anyone want to work with me on a hint wizard? Let me know—it would be of great benefit to many people.

Feb 18 2008, 09:54:55 AM PST Permalink

Download NetBeans!

20080217 Sunday February 17, 2008

JetBrains & NetBeans

On Friday night I met with Roumen and Vaclav Pech (IntelliJ evangelist), Ann Oreshnikova (JetBrains Marketing Director), and Michael Hüttermann (Cologne JUG leader, among many other things). We had a really good time, it was extremely cool and interesting. Vaclav's camera ended up with a lot of pictures, here's the one where I look least crazy:

It was also cool because Michael and Ann are the Zone Leaders of JetBrains Zone, so these were the first DZone Leaders that I've met, outside of NetBeans Zone. I hope this is the first of many such get togethers.

In other news. The NetBeans File Template Module Tutorial has been updated to include information about FreeMarker and what it can do for the file templates you create for your users.

Feb 17 2008, 12:35:09 AM PST Permalink

Download NetBeans!

20080216 Saturday February 16, 2008

Solving My Pet Peeve In NetBeans IDE: Missing Inner Class Hint

My pet peeve in NetBeans IDE is that the hint that appears when I'm instantiating a non-existent class can only create a new class in a new file:

But I don't want a new file. I want the class to be created in the same file, as an inner class. But the IDE doesn't offer that hint. So I inspected the sources and found that all the code for the generation of the inner class is actually already there. It seems to simply be a bug. The appropriate code isn't called at the right time. I created a new module and transferred just the relevant sources to my new module, fixing the code to the point where it works in the way that I think it should. Now, I get the choice I always wanted:

When I choose the new inner class hint, the following is created by the IDE at the bottom of the file, instead of in a new file:

class SliderChangeListener {

    public SliderChangeListener() {
    }
}

It's exactly the same code as before, except that this time it is an inner class. (It would be cool if the correct implementation or extension class were added to the signature, but that isn't the case for the normal hint here either and here I simply want to replicate the existing functionality in an inner class.)

It would be very cool if some people would try this plugin and see if it works. No promises, so don't be disappointed if it doesn't. It's working for me anyway and I'm going to find it pretty handy, though the Beans Binding blog entries I introduced over the last few days should reduce the need for inner classes, by the way! Please make sure you're using NetBeans IDE 6.0.1 and then get the plugin from the Plugin Portal:

http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=6152.

Feb 16 2008, 06:36:30 AM PST Permalink

Download NetBeans!

20080215 Friday February 15, 2008

Maven is Magic in NetBeans IDE 6.0

I came across Mark Ashworth's Connext-Graphs project a few days ago and then found that the project sources, including samples, are provided as Maven projects. Mark told me that I would first need to do a "mvn install" on the main project, after which I would be able to build the other project, i.e., the one that provides samples. So, being ignorant of Maven and all it entails, I thought: "Hmmm. I guess I'll go to the Synaptic Package Manager and see if I can find Maven there." I also googled around a bit.

Of course, I was aware of the fact that there's a NetBeans plugin for Maven. However, I assumed I'd need to install some command line tool first, which would then need to be registered in the IDE, after I'd installed the Maven plugin. At some point, I thought: "Maybe I'll install the Maven plugin first. Then I'll look for the place where I need to register the command line tool and go from there." So I went to the IDE's Plugin Manager, searched for Maven, and then had it installed 3 seconds later.

Next, I went to the New Project wizard. I thought: "Maybe I'll be able to import the Maven projects that I've downloaded from Mark's site. Once I've imported them, maybe I'll be able to build." Sure enough, I found a project type for importing Maven projects:

I clicked Next. Guess what I found? A message telling me: "Hey buddy, you don't need to import at all. Just open the project, NetBeans is smarter than you think!" Or, words to that effect:

So, I could simply open both projects, without doing anything special at all. I chose File | Open Project and then browsed to where I'd downloaded the projects from the dev.java.net site. I found that the IDE recognized the projects, because the typical NetBeans project icon showed for both of them and I thus was able to open them. Once opened in the IDE, they look like this:

And... there are contextual menu items for tasks such as building the projects:

In the end, I didn't need to do anything at all. I simply opened the projects, built them, and then ran the samples. It was an utterly boring experience, I had no issues to file in Issuezilla and I had no problems to solve at all, in any shape or form. Life sucks sometimes.

Feb 15 2008, 08:13:46 AM PST Permalink

Download NetBeans!

20080214 Thursday February 14, 2008

NetBeans Zone

Some might know that I've been posting articles on Javalobby since some time last year. I've found it to be a great experience. The exposure your thoughts get out there is so much broader than within a language-specific or tool-specific community. The range of topics I've written on have been pretty broad and the number of people I've "met" (i.e., virtually) for interviews has been interesting.

Recently, Javalobby reconstituted itself, moving from a Java focus to something far broader. Beyond "just" Java, there are now areas (called "zones") for a number of other languages too, as well as technologies and tools. For example, there's http://java.dzone.com/, containing all the Java-specific content. But there's also http://groovy.dzone.com/ and http://ruby.dzone.com/, for example. Communities are forming around these zones, with the Groovy zone being a particularly good example.

Similarly, there's also http://netbeans.dzone.com/. NetBeans Zone focuses specifically on NetBeans-specific topics. As with all the other zones, there are "zone leaders", in our case being Tim, Arun, and myself. Zone Leaders are community members who participate regularly in the management of zones and they usually have significant interest and expertise in one or more of the topics covered by the zone in question and they enjoy sharing their knowledge with others. (Go here if you want to be a zone leader of any of the zones.)

So, this is an open invitation to submit articles to NetBeans Zone. Quite a few have done already, such as Gregg on the hidden hippie and Tim on reusing NetBeans plugins in standalone apps and in the IDE. Some of the content there is taken straight from the author's blog, by the author himself. There's nothing wrong with that, the benefit for you is that your thoughts and learnings will get more eyeballs that way. Other articles there are completely new. For example, do you know how to add resize functionality to applications based on the Visual Library? And do you know about the tools for adding color to web applications in NetBeans IDE? A cool thing is that there's lots of crosspollination between zones so that, for example, Steven from Groovy Zone's interview with Martin about his Groovy/Grails plugin is posted both in NetBeans zone and in Groovy zone. (Go here to read it.)

Here are some high level general guidelines for if you want to write articles for NetBeans Zone:

  • Upload a pic of yourself into your DZone user profile.
  • English only, please, and sorry.
  • Technical articles, or at least mostly-technical articles, are welcome. If your article has code, that's better than if it doesn't. No marketing stuff. If a technical programming person wouldn't enjoy reading it, then maybe you shouldn't post it on NetBeans Zone.
  • A post must not consist of little more than a link pointing somewhere else. The point of the story should be in the story itself.
  • Try and include at least one picture but not more than about three or four, unless adding more is absolutely functional to the story.
  • Try and keep your formatting simple. No numbered lists within numbered lists, for example.

Finally, why would you want to write articles for NetBeans Zone? Again, to share your new discoveries or your insights or whatever. It could be completely unique or it could be copy-pasted from your own blog or elsewhere. Before an article is accepted, one of the zone leaders needs to approve it. The points above are important in determining whether something is accepted and in 99% of the cases, there shouldn't be a problem on that score.

So, feel free to come over and join in and share your NetBeans discoveries at http://netbeans.dzone.com/!

Feb 14 2008, 11:01:59 AM PST Permalink