NetBeans Java Hint Module Tutorial

Last reviewed on 2022-02-07

This tutorial demonstrates how to create a NetBeans module that provides one or more Java hints. At the end of the first scenario covered in this tutorial, whenever the user types " JOptionPane.showMessageDialog ", a Java hint will appear, asking the user whether the statement is needed (or whether it is there because the user needed it for debugging):

At the end of the second scenario, the user will be able to click the hint to delete the superfluous statement:

In addition, the user will be able to find and replace multiple instances of the superfluous statement, after using the "Inspect & Transform" dialog in the Refactor menu, which displays all found instances in the Refactoring window:

For troubleshooting purposes, you are welcome to download the completed tutorial source code.

Learning About Java Hints

The best way to learn about the Java hint infrastructure is to read the sources of the Java hints that are part of the IDE. Start by identifying a hint that does something similar to what you would like to achieve, then check out the NetBeans sources, and read the source code of the related hint.

  1. Use git to clone the NetBeans sources: as explained here.

  1. In the NetBeans sources, browse to the "java.hints" folder (see the java.hints sources here), expand it, and read the sources relevant to the hint you want to create.

The remainder of the tutorial shows you how to put a Java hint together generally, from scratch, but remember that the best place to look for useful snippets of code is in the sources above, if there is anything specific that you don’t know how to do.

Setting Up the Module Project

Before you start writing the module, you have to make sure you that your project is set up correctly. The IDE provides a wizard that sets up all the basic files needed for a module.

  1. Choose File > New Project (Ctrl+Shift+N). Under Categories, select NetBeans Modules. Under Projects, select Module. Click Next.

  1. In the Name and Location panel, type MyCustomHints in the Project Name field. Change the Project Location to any directory on your computer. Click Next.

  1. In the Basic Module Configuration panel, type org.my.custom.hints in Code Name Base. Click Finish.

The IDE creates the MyCustomHints project. The project contains all of your sources and project metadata, such as the project’s Ant build script. The project opens in the IDE. You can view its logical structure in the Projects window (Ctrl-1) and its file structure in the Files window (Ctrl-2).

Scenario 1: Identifying Statements of Interest

In this section, you provide the Java code for the Java hint.

  1. Right-click the project node and choose New > Other > Module Development > Java Hint. Click Next.

  1. In the New Java Hint Description panel, fill out the fields as follows:

hint 72 new hint 2

Click Next.

  1. In the New Java Hint Location panel, fill out the fields as follows:

hint 72 new hint 3

Click Finish.

The newly created class has the content below:

package org.my.custom.hints;

import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.java.hints.ConstraintVariableType;
import org.netbeans.spi.java.hints.ErrorDescriptionFactory;
import org.netbeans.spi.java.hints.Hint;
import org.netbeans.spi.java.hints.HintContext;
import org.netbeans.spi.java.hints.TriggerPattern;
import org.openide.util.NbBundle.Messages;

@Hint(displayName = "#DN_JOptionPaneHint",
      description = "#DESC_JOptionPaneHint",
      category = "general")
@Messages({
    "DN_JOptionPaneHint=JOptionPane.showMessageDialog",
    "DESC_JOptionPaneHint=Checks for JOptionPane.showMessageDialog."
})
public class JOptionPaneHint {

@TriggerPattern(
            value = "$str.equals(\"\")", //Specify a pattern as needed
            constraints =
@ConstraintVariableType(
                    variable = "$str",
                    type = "java.lang.String"))
@Messages("ERR_JOptionPaneHint=Is JOptionPane.showMessageDialog needed?")
    public static  ErrorDescription computeWarning(HintContext ctx) {
        return  ErrorDescriptionFactory.forName(
                ctx,
                ctx.getPath(),
                Bundle.ERR_JOptionPaneHint());
    }

}
  1. Replace the @TriggerPattern with the following:

@TriggerPattern(
            value = "$1.showMessageDialog",
            constraints =
                @ConstraintVariableType(
                    variable = "$1",
                    type = "javax.swing.JOptionPane"))
  1. Right-click the module and choose Build. Open the Files window (Ctrl-2) and browse to the generated layer file, which is shown below:

hint 72 layer 1

Open the layer file and you should see that the following has been generated from the annotations in the class:

<folder name="org-netbeans-modules-java-hints">
    <folder name="code-hints">
        <folder name="org-my-custom-hints-JOptionPaneHint.class">
            <folder name="org-netbeans-spi-java-hints-Hint.annotation">
                <!--org.my.custom.hints.JOptionPaneHint-->
                <attr
                    bundlevalue="org.my.custom.hints.Bundle#DN_JOptionPaneHint" name="displayName"/>
                <attr
                    bundlevalue="org.my.custom.hints.Bundle#DESC_JOptionPaneHint" name="description"/>
                <attr name="category" stringvalue="general"/>
            </folder>
            <folder name="computeWarning.method">
                <folder name="org-netbeans-spi-java-hints-TriggerPattern.annotation">
                    <folder name="constraints">
                        <folder name="item0">
                            <folder name="org-netbeans-spi-java-hints-ConstraintVariableType.annotation">
                                <!--org.my.custom.hints.JOptionPaneHint-->
                                <attr name="variable" stringvalue="$1"/>
                                <attr name="type" stringvalue="javax.swing.JOptionPane"/>
                            </folder>
                            <!--org.my.custom.hints.JOptionPaneHint-->
                        </folder>
                        <!--org.my.custom.hints.JOptionPaneHint-->
                    </folder>
                    <!--org.my.custom.hints.JOptionPaneHint-->
                    <attr name="value" stringvalue="$1.showMessageDialog"/>
                </folder>
                <!--org.my.custom.hints.JOptionPaneHint-->
            </folder>
            <!--org.my.custom.hints.JOptionPaneHint-->
        </folder>
    </folder>
</folder>
  1. Switch back to the Projects window, right-click the module, and choose Run. A new instance of the IDE starts up. The module is installed automatically. Create a new Java application. Type JOptionPane.showMessageDialog somewhere in your code. You should see the showMessageDialog is underlined and you should also see the hint' displayed:

hint 72 result 1

When you click on the icon in the left sidebar, the popup below appears. Press the Right key on the keyboard while the popup is shown to expand it, so that you can configure it if necessary:

hint 72 result 2

Go to Source | Inspect, click Single Inspection, and then click the Browse button. Use the Search field to find your new inspection:

hint 72 result 4

Set the Scope to "Open Projects", so that all projects will be searched for the statement of interest, and check that your inspection is shown:

hint 72 result 5

Click Inspect and notice that all instances of the statement of interest are found:

hint 72 result 6

Double-click an item in the list above and the corresponding file opens, with the cursor on the line where the statement of interest has been found.

Though you are able to find statements throughout your projects, you’re not able to fix them yet. That topic is covered in the next scenario.

Scenario 2: Fixing Identified Statements

In this section, you learn how to fix statements of interest that have been identified via the instructions in the previous section.

  1. Add the Java fix below as an inner class of the class created in the previous section.

private static final class FixImpl extends JavaFix {

    public FixImpl(CompilationInfo info, TreePath tp) {
        super(info, tp);
    }

    @Override
    @Messages("FIX_ShowMessageDialogChecker=Remove the statement")
    protected String getText() {
        return Bundle.FIX_ShowMessageDialogChecker();
    }

    @Override
    protected void performRewrite(TransformationContext tc) throws Exception {
        WorkingCopy wc = tc.getWorkingCopy();
        TreePath statementPath = tc.getPath();
        TreePath blockPath = tc.getPath().getParentPath();
        while (!(blockPath.getLeaf() instanceof BlockTree)) {
            statementPath = blockPath;
            blockPath = blockPath.getParentPath();
            if (blockPath == null) {
                return;
            }
        }
        BlockTree blockTree = (BlockTree) blockPath.getLeaf();
        List<? extends StatementTree> statements = blockTree.getStatements();
        List<StatementTree> newStatements = new ArrayList<StatementTree>();
        for (Iterator<? extends StatementTree> it = statements.iterator(); it.hasNext();) {
            StatementTree statement = it.next();
            if (statement != statementPath.getLeaf()) {
                newStatements.add(statement);
            }
        }
        BlockTree newBlockTree = wc.getTreeMaker().Block(newStatements, blockTree.isStatic());
        wc.rewrite(blockTree, newBlockTree);
    }

}

The code above comes from the NetBeans sources, where it is used in the SystemOut class, in the "java.hints" module, for removing found instances of System.out .

  1. Add the fix to the error description you defined in the previous section; you only need to add the code highlighted below:

public static ErrorDescription computeWarning(HintContext ctx) {
    Fix fix = new FixImpl(ctx.getInfo(), ctx.getPath()).toEditorFix();
    return ErrorDescriptionFactory.forName(
            ctx,
            ctx.getPath(),
            Bundle.ERR_JOptionPaneHint(),
            *fix*);
}
  1. Check that you have these import statements:

import com.sun.source.tree.BlockTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.util.TreePath;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.Fix;
import org.netbeans.spi.java.hints.ConstraintVariableType;
import org.netbeans.spi.java.hints.ErrorDescriptionFactory;
import org.netbeans.spi.java.hints.Hint;
import org.netbeans.spi.java.hints.HintContext;
import org.netbeans.spi.java.hints.JavaFix;
import org.netbeans.spi.java.hints.JavaFix.TransformationContext;
import org.netbeans.spi.java.hints.TriggerPattern;
import org.openide.util.NbBundle.Messages;
  1. Install the module again and you will be able to click the hint to delete the superfluous statement:

hint 72 result 7

In addition, you should be able to find and replace multiple instances of the superfluous statement, after using the "Inspect & Transform" dialog in the Refactor menu, which displays all found instances in the Refactoring window:

hint 72 result 3

In this tutorial, you have been introduced to the NetBeans Java hint infrastructure. To obtain a deeper understanding, see the sources referred to at the start of this tutorial, as well as the resources referred to below.

See Also

For more information about creating and developing NetBeans Module, see the following resources: