Source code file content

Revision: 2

import
» Project Revision History

» Checkout URL

web-content / trunk / docs / howto-code-analyzer / howto-code-analyzer.html

Size: 15269 bytes, 1 line
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=windows-1251">
    <title>
	How to Write a Simple Code Analyzer Using NetBeans Language Model API
    </title>
    
    <STYLE type="text/css">
        H1 { text-align: center}
        H2 { text-align: center}
        H3 { text-align: center}
        H4 { text-align: center}
        H5 { text-align: center}
    </STYLE>
 
</head>
<body>

<H1>How to Write a Simple Code Analyzer <br> Using NetBeans&trade; C/C++ Language Model API API</H1>

<p>This tutorial will show you how easy you can extend NetBeans C/C++ functionality 
using the Language Model API.</p>

<p>You can read an overview of this API in the article 
<a href="../code-assistance-architecture/code-assistance-architecture.html">NetBeans&trade; C/C++ Language Model API Overview</a>
and find a javadoc reference of the API by this link:
<a href="../org-netbeans-modules-cnd-api-model/index.html?overview-summary.html">NetBeans&trade; C/C++ Language Model API Reference</a>
</p>

<p>We are going to create a module that will check C++ source code 
for complying a very simple rule. The rule reads as follows:
the destructor should always be virtual.
You can find a lot of very usefil rules in the 
"Effective C++" by Scott Meyers and other books of the same aughtor.
The information about the books is available at 
<a href="http://www.aristeia.com/books_frames.html">
http://www.aristeia.com/books_frames.html</a>    
</p>

<p>Well, the rule as we stated it is a bit simplified - 
if your class isn't supposed to be extended, it does not matter,
whether the destructor is virtual or not.
But we'll use this simplified rule to not dive too deep into details.
</p>

<p>Below we'll describe the creation of the plugin step by step.</p>


<h2>Creating Netbeans plug-in Module and Action</h2>

<p>This part describes the creation of the Netbeans plug-in module and action.
If it's sounds trivial, just skip it. The only thing you should know in this case
is that you should add two modules to the dependencies:
C/C++ Code Model API (<code>org.netbeans.modules.cnd.api.model</code>) and
C/C++ Code Model Utilities (<code>org.netbeans.modules.cnd.modelutil</code>).
Note that, since tha API is still experimental, you have to check "Show non-API modules"
check box and add an <i>implementation</i> dependencies to these modules.
</p>

<ol>
    <li>
        Choose File > New Project. In the New Project wizard, 
	select NetBeans Plug-in Modules under Categories and Module under Projects. 
        Click Next.
    </li>    
    <li>
	Select module location.
        Type the name of the module (for example, "CodeAnalyzer"). 
        Click Finish.
    </li>
    <li>
	We will need certain dependencies in this project. 
        In the Projects window, select Project Properties from context menu,
	select Libraries note, and press Add button.
        C/C++ modules APIs are under development, so you will need to select Show Non-API Modules 
	in the dialog box to see them in the Module list.
        Add the following libraries:
        <ul>
	    <li>C/C++ Code Model API (<code>org.netbeans.modules.cnd.api.model)</code></li>
	    <li>C/C++ Code Model Utilities (<code>org.netbeans.modules.cnd.modelutil</code>)</li>
	    <li>Datasystems API (<code>org.openide.loaders</code>)</li>
	    <li>File System API (<code>org.openide.filesystems</code>)</li>
	    <li>I/O APIs (<code>org.openide.io</code>)</li>
	    <li>Nodes API (<code>org.openide.nodes</code>)</li>
	    <li>Project API (<code>org.netbeans.modules.projectapi</code>)</li>
	    <li>Utilities API (<code>org.openide.util</code>)</li>
        </ul>
	You'll now have to specify that the dependency on
        "C/C++ Code Model API" and "C/C++ Code Model Utilities" modules
	is an <i>implementation dependency</i>.
	To do this, select each of the two modules in the libraries list,
        press Edit button, and check "Implementation Version" check box.
    </li>
    <li>
	We will add an action that is enabled when a project is selected;
	this action will perform a code analysis.
        To add an action, in Project Window, select New > Action from the context menu.
        Select "Conditionally Enabled", "Project" in Cookie Classes drop-down box
	and "User may Select Multiple Nodes" radio button: 
	<p><img src="action-step-2.png"></p>
	Press next. Select "Tools" in Category.
        Leave the default "Global Menu Item" check box checked,
	and select "Tools" in "Menu" drop-down. Press Next.
	Enter your action class name (for example, "AnalyzeAction")
	and display name (for example, "Analyze Code")
    </li>
</ol>

<p>Now your action is ready. You may try launching your project.
You will see the "Analyze Code" action in the "Tools" menu.</p>

<h2>Creating a Very Simple Analyzer</h2>

<p>We will now write an analyzer - a class that analyzes C++ code and
creates a list of issues found.
</p>

<h3>Issue class</h3>

<p>Let us create a simple class that represents a single found issue with source code.
Since we work with Language Model, let us talk in its terminology.
We will use a <a href="../org-netbeans-modules-cnd-api-model/org/netbeans/modules/cnd/api/model/CsmOffsetable.html">
CsmOffsetable</a> interface to represent an element the issue is about.
The <a href="../org-netbeans-modules-cnd-api-model/org/netbeans/modules/cnd/api/model/CsmOffsetable.html">
CsmOffsetable</a> interface represents a C/C++ language construct that occupy
a continuos extent in source or header file. 
</p>

<pre><code>
    import org.netbeans.modules.cnd.api.model.CsmOffsetable;
    
    /**
     * An issue - a simple class that represents
     * a single issue found in code
     */
    public class Issue {

            private String description;
            private CsmOffsetable element;

            public Issue(CsmOffsetable element, String gescription) {
                this.description = gescription;
                this.element = element;
            }

            /** Returns the description of the issue */
            public String getDescription() {
                return description;
            }

            /** Returns an element this issue is about */
            public CsmOffsetable getElement() {
                return element;
            }
    }    
</code></pre>

<h3>An Analyzer Class</h3>

<p>Let us create a Analyzer class that performs code analysis.
It analyzes a project and creates a list of issues found.
So the key methods are two: getIssues() and analyzeProject().
The Analyzer works in terms of Language Model.
So we pass an instance of <a href="../org-netbeans-modules-cnd-api-model/org/netbeans/modules/cnd/api/model/CsmProject.html">
CsmProject</a> to the analyzeProject() method.</p>

<p>The algorythm then drills down through the project code hierarchy,
searches for classes (
<a href="../org-netbeans-modules-cnd-api-model/org/netbeans/modules/cnd/api/model/CsmClass.html">
CsmClass</a> instances) and analyzes found classes.</p>

<pre><code>
    /**
     * Analyzes project code.
     * Fills a collection of issues found.
     */
    public class Analyzer {

        /** A collection of issues to fill */
        Collection&lt;Issue&gt; issues = new ArrayList&lt;Issue&gt;();

        /** Returns a list of issues found */
        public Collection&lt;Issue&gt; getIssues() {
            return issues;
        }

        /** The main entry point. Analyzes a project. */
        public void analyzeProject(CsmProject project) {
            // The project tree root is it's global namespace
            analyzeNamespace(project.getGlobalNamespace());
        }

        /** Analyzes the given namespace */
        private void analyzeNamespace(CsmNamespace namespace) {
            // go through this namespace declarations
            analyzeDeclarations(namespace.getDeclarations());
            // go through all nested namespaces
            for( CsmNamespace child : namespace.getNestedNamespaces() ) {
                analyzeNamespace(child);
            }
        }

        /** Analyzes the given collection of declarations */
        private void analyzeDeclarations(Collection&lt;CsmOffsetableDeclaration&gt; declarations) {
            for( CsmDeclaration decl : declarations ) {
                analyzeDeclaration(decl);
            }
        }

        /** Analyzes the given declaration */
        private void analyzeDeclaration(CsmDeclaration decl) {
            // Don't use instanceof - use CsmKindUtilities instead!
            if( CsmKindUtilities.isClass(decl)) {
                analyzeClass((CsmClass) decl, issues);
            }
            if( CsmKindUtilities.isNamespaceDefinition(decl)) {
                CsmNamespaceDefinition namespaceDefinition = (CsmNamespaceDefinition) decl;
                analyzeDeclarations(namespaceDefinition.getDeclarations());
            }
        }

        /** Analyzes the given class */
        private void analyzeClass(CsmClass cls, Collection&lt;Issue&gt; issues) {
            // go through the class members
            for( CsmMember mem : cls.getMembers() ) {
                if( CsmKindUtilities.isDestructor(mem) ) {
                    CsmMethod dtor = (CsmMethod) mem;
                    if( ! dtor.isVirtual() ) {
                        issues.add(new Issue(dtor, "Class "+ cls.getName() + " has non-virtual destructor"));
                    }
                }
            }
        }
    }
</code></pre>

<h2>Joining Analyzer and AnalyzeAction</h2>

<p>Now we need to call an Analyzer from our AnalyzeAction.
First, we should warn you of extensive use of the Language Model API
in the UI thread. The Language Model API method can be costly, so
you should always call them from a separate thread:
</p>

<pre><code>
    protected void performAction(final Node[] activatedNodes) {
	RequestProcessor.getDefault().post(new Runnable() {
	    public void run() {
		process(activatedNodes);
	    }
	});
    }
    
    private void process(Node[] activatedNodes) {
        // TODO: write the logic here
    }
</code></pre>

<p>Now we have an Analyzer class that works in terms of Language Model
(i.e. expects an instance of 
<a href="../org-netbeans-modules-cnd-api-model/org/netbeans/modules/cnd/api/model/CsmProject.html">
CsmProject</a> as a parameter). How to find the project to analyze?
It is quite staightforward. Use Lookup for getting a NetBeans project;
then use <a href="../org-netbeans-modules-cnd-api-model/org/netbeans/modules/cnd/api/model/CsmModel.html#getProject(java.lang.Object)">
CsmModel.getProject()</a> to find the element of Language Model that corresponds to the NetBeans project:
</p>

<pre><code>
    private void process(Node[] activatedNodes) {
	Analyzer analyzer = new Analyzer();
	for (int i = 0; i < activatedNodes.length; i++) {
	    Project project = activatedNodes[i].getLookup().lookup(Project.class);
	    if( project != null ) {
                CsmProject csmProject = CsmModelAccessor.getModel().getProject(project);
		if (csmProject != null) {
		    analyzer.analyzeProject(csmProject);
		}
	    }
	}
	Collection&lt;Issue&gt; issues = analyzer.getIssues();
	// TODO: write the code to display issues
    }
</code></pre>

<h2>Displaying output</h2>
    
<p>Now there is the onlything left: displaying the isses.
There are different approaches. You may create a <a href="http://bits.netbeans.org/dev/javadoc/org-openide-windows/index.html?org/openide/windows/TopComponent.html">
TopComponent</a> of your own, or use the NetBeans <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-spi-tasklist/index.html?overview-summary.html">
Task List API</a>, or just use the NetBeans <a href="http://bits.netbeans.org/dev/javadoc/org-openide-io/index.html?overview-summary.html">
I/O APIs</a>. We will use the latter, as it is the most straightforward.</p>

<p>Anyhow neither AnalyzeAction nor Analyzer should know how to display iusses.
We will create a separate class that is responsible for this - IssuesDisplayer.</p>

<p>The IssuesDisplayer prints each issue to the Output Pane.
It also associates an <a href="http://bits.netbeans.org/dev/javadoc/org-openide-io/org/openide/windows/OutputListener.html">
OutputListener</a> with each line. When user control-clicks the line, the listener opens the 
correspondent element in editor.
</p>

<pre><code> 
    public class IssuesDisplayer {

        public static final Logger LOG = Logger.getLogger("org.yourorghere.codeanalyzer");

        public void display(Collection&lt;Issue&gt; issues) {
            if (!issues.isEmpty()) {
                InputOutput io = IOProvider .getDefault().getIO("Code Analyzer Results", false);
                io.select(); //Tree tab is selected
                OutputWriter writer = io.getOut();
                try {
                    writer.reset();
                    for (Issue issue : issues) {
                        final CsmOffsetable element = issue.getElement();
                        StringBuilder sb = new StringBuilder();
                        Formatter formatter = new Formatter(sb);
                        formatter.format("Warning %s:%d %s\n", element.getContainingFile().getAbsolutePath(), element.getStartPosition().getLine(), issue.getDescription());
                        writer.println(sb.toString(), new OutputListener() {
                            public void outputLineSelected(OutputEvent ev) {}
                            public void outputLineCleared(OutputEvent ev) {}
                            public void outputLineAction(OutputEvent ev) {
                                CsmUtilities.openSource(element);
                            }
                        });
                    }
                }
                catch( IOException ex ) {
                    LOG.log(Level.SEVERE, "error when filling output window\n {0}", new Object[] { ex }); // NOI18N
                }
            }
        }
    }      
</code></pre> 

<p>Now call the IssuesDisplayer from the <code>process()</code> method shown above:</p>

<pre><code> 
    private void process(Node[] activatedNodes) {
	Analyzer analyzer = new Analyzer();
	for (int i = 0; i < activatedNodes.length; i++) {
	    Project project = activatedNodes[i].getLookup().lookup(Project.class);
	    if( project != null ) {
                CsmProject csmProject = CsmModelAccessor.getModel().getProject(project);
		if (csmProject != null) {
		    analyzer.analyzeProject(csmProject);
		}
	    }
	}
	Collection&lt;Issue&gt; issues = analyzer.getIssues();
        if (!issues.isEmpty()) {
            new IssuesDisplayer().display(issues);
        }
    }    
</code></pre> 

<p>We are done!</p>

<h2>Try this out</h2>

<p>
Let's try this out:
<ul><ul>
    <li>
        Launch your project (press F6 in the NetBeans IDE).
    </li>
    <li>
        Create a simple application. 
	Add a new C++ file and write a class with a destructor that isn't virtual.
    </li>
    <li>
        Select the project in Projects window. Select Tools > Analyze Code.
	The warning message appears in Output Window.
    </li>
    <li>
        Control-click the warning. 
	The code will be opened and the caret will be located at destructor.
    </li>
</ul></ul>
</p>

</body>
</html>

Project Features

About this Project

CND was started in November 2009, is owned by DimaZh, and has 197 members.
By use of this website, you agree to the NetBeans Policies and Terms of Use (revision 20160708.bf2ac18). © 2014, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo
 
 
Close
loading
Please Confirm
Close