Finding Distributed Packages/Namespaces with the Sonargraph Scripting Engine

Today I will show how to make use of a very powerful, yet underutilized capability of Sonargraph-Architect. By writing simple Groovy scripts you are able to create your own code checkers or define your own metrics. Many of our most useful scripts are just about 50 lines of code and therefore not a big effort to create. As an example we will develop a script that finds packages (Java) or name spaces (C#, C++) that occur in more than one module.

The scripting engine of Sonargraph is based on our scripting API. Most scripts are based on the visitor pattern. Using this pattern a script can traverse specific elements of Sonargraph’s software system model, which is basically a very big tree data structure. At the root there is the software system node, which is accessible by a globally available instance of class CoreAccess, called “coreAccess”. This specific instance is language agnostic, i.e. it can be used for scripts that support all programming languages supported by Sonargraph. When creating a script you decide wether it will be language specific or language agnostic. Language specific scripts have access to more detailed language specific data and will use different root objects like “javaAccess” or “csharpAccess”.

Depending on the task to be solved the script will use one of the three available main branches of Sonargraph’s model tree. The parser model, which is basically representing the physical aspects of the software system like source files, root directories and programming elements rooted in their physical home, which usually is a source file. The two other branches are based on a logical model. The logical model skips physical aspects and only contains logical elements like namespaces/packages and programming elements like classes. The first variant also still retains modules while the second variant only makes a difference between internal and external elements. Your own code base defines all the “internal” elements, while all elements that referenced by your code based without being defined in your code base are considered external.

Parser Model (physical) versus Logical Model (with Modules)

Since we want to find packages/namespaces spread over more than one module we will use the logical branch with modules. To solve our problem we will create a map that maps namespace/package names to the modules where they occur in. Each namespace that belongs to more than one module after we have visited all namespaces will get a warning marker.

So let’s begin creating a script. Navigate to the “Files” tab (top left section) in Sonargraph-Architect and right click on “Scripts”. A popup menu will appear where you can select “New Script File…”. We give a name to our script and are happy with the fact that only “Core” is selected for languages. Our script will be language agnostic. The editor will show some example code which you can delete, we won’t need it for our purposes.

  1. // Find all packages/namespaces that occur in more than one module
  2. ICoreVisitor v = coreAccess.createVisitor()
  3.  
  4. def namespaceMap = [:]
  5. def distributedPackages = [:]
  6.  
  7. v.onLogicalModuleNamespace
  8. {
  9.     LogicalNamespaceAccess ns ->
  10.  
  11.     // A 'part' namespace does not contain classes
  12.     if (!ns.isPart())
  13.     {
  14.         def name = ns.name
  15.         def entry = namespaceMap[name]
  16.  
  17.         if (entry == null)
  18.         {
  19.             // seeing this namespace name for the first time
  20.             entry = [ ns ]
  21.             namespaceMap[name] = entry
  22.         }
  23.         else
  24.         {
  25.             // append namespace to list
  26.             entry << ns
  27.             // use map as a set
  28.             distributedPackages[name] = 1
  29.         }  
  30.     }
  31.     // very important, since namespaces can be nested
  32.     v.visitChildren(ns)
  33. }

In line 2 we create the all important visitor object by utilizing the global variable “coreAccess” which represents our software system. From line 7 on we define a closure that is executed for each instance of “LogicalModuleNamespace” in our software system model. The visitited instance is passed as a parameter named “ns” (line 9). Before defining the closure we declare to global maps, “namespaceMap” and “distributesPackages”. The first map will map namespace names to a list of modules, while the second one is just use as as set to remember the names of all distributes namespaces.

In line 17 we are testing for “part” namespaces. Those are namespaces that do not contain any classes or other programming elements, but only nested namespaces. We skip those because they would just lead to irrelevant false positives. The remaining code should be pretty self explanatory. Just notice the call to “visitChildren” in line 32. This is very important, otherwise we would only visit top-level namespaces and never reach nested namespaces.

  1. v.onLogicalExternal
  2. {
  3.     // skip externals
  4. }
  5.  
  6. // Traverse the model and build a data model to be used below
  7. coreAccess.visitLogicalModuleNamespaces(v)

This next section adds another closure that allows us to skip all external elements by not calling “visitChildren” inside of the closure. Line 41 starts the tree visitation process. After this call returns our two global maps will be filled with the needed data.

  1. // Now do the work and create issues and a result tree
  2. distributedPackages.keySet().each
  3. {
  4.     // 'it' represents the name of a distributed package
  5.     distributedNamespaces = namespaceMap[it]
  6.     // distributedNamespaces is a list of LogicalNamespaceAccessobjects
  7.     moduleNames = distributedNamespaces.collect
  8.     {
  9.         it.moduleScope.name
  10.     }
  11.     msg = "Namespace occurs in more than one module: $moduleNames"
  12.     // Under each name node add the modules where this package name occurs
  13.     node = result.addNode(it)
  14.     distributedNamespaces.each
  15.     {
  16.         // Add an issue to the logical namespace
  17.         result.addWarningIssue(it, "Distributed Package", msg)
  18.         // Add the module as a child node in the result tree
  19.         result.addNode(node, it.moduleScope)
  20.     }
  21. }

The purpose of this loop is to make use of our results. For the script view we will create a result tree that will associate distributed package names with all the modules they occur in. Moreover we will add issues to the distributed logical namespace objects.

Screenshot of the Sonargraph-Architect script result view

At the end we will also add a metric to Sonargraph that counts the number of distributes package names in a software system. The “good” range will be zero to zero, we consider distributed packages as a bad thing.

  1. // Now define a metric where every value above zero will create a warning
  2. def metricId = coreAccess.getOrCreateMetricId("DistributedPackageNames", "Number of distributed package names", "Number of package names that occur in more than one module", false, 0, 0)
  3.  
  4. // Add the metric value to the result
  5. result.addMetricValue(metricId, coreAccess, distributedPackages.size())

A good practice to develop scripts is to use “println” statements in your code. Those will go to the console view when you run the script. When everything works as expected you can remove the “println” statements.The editor also supports code completion by pressing Ctrl-Space.

After our script is polished we can add it to the automated script runner of Sonargraph-Architect, i.e. the script will run automatically after each refresh and will contribute issues to the Sonargraph issues view and metrics to the metrics view. The script runner configuration can be accessed via the “System/Configure…” menu. Once it is added to the script runner the script will also run during automated builds with Sonargraph-Build or in our plugins for Eclipse and IntelliJ.

Screenshot of Sonargraph-Architect showing the script results

Now we have come to the end of our litte example. Please leave feedback or questions in the comment section below. If you want to try our scripting engine yourself you can obtain a free evaluation license from our website and try it on your own project. The script above is part of our core quality model and can be used out of the box with Sonargraph-Architect.

Leave a Reply

Your email address will not be published.