Automatic Detection of Singletons

Today, we released a new version of Sonargraph with an improved script to find Singletons. “Singleton” is one of the design patterns described by the “Gang of Four” [1]. It represents an object that should only exist once.
There are a couple of pros and cons for a Singleton that I won’t go into detail in this blog post. For anyone interested, I recommend “Item 3: Enforce a singleton property with a private constructor or an enum type” in “Effective Java”, written by Joshua Bloch [2]. Two interesting links that came up during a quick internet research are listed as references [3] [4]. Let’s just summarize that it is important to ensure that Singletons are properly implemented to avoid bad surprises (a.k.a bugs) in your software. And you should keep an eye on the existing Singletons and check that they are not misused as global variables.

This blog post describes, how you can detect Singletons by utilizing the Groovy scripting functionality of Sonargraph.

Singletons that should be detected

Sonargraph is a static analysis tool that supports several programming languages. To keep it simple, I will focus on Java for the rest of this post. The different Singleton implementations that should be detected by the script are the following:

  1. Single-element enum as recommended by Joshua Bloch, because you don’t need to worry about serialization issues.
  2. Type with a public final instance field.
  3. Type with a public static factory method.

To ensure that no additional instances can be created, it is required that the constructor is private. If this is not the case, it’s a programming error and we want to create a warning. The following screenshot shows minimal samples for implementing the three approaches. Note that you need to do some synchronization for option three, if your code should run safely in a multi-threaded environment (search for double-check locking).

Minimal implementations of the Singleton pattern

I will describe the development of the script next.

Developing the Script

As a precondition for the Script development, you need to setup a Java software system in Sonargraph. There are several importers (Eclipse, IntelliJ, Maven) that makes this step very straight forward. Then you can select the “Files” view, right-click on the “Scripts” folder and select to create a new script. Provide a name and select the language “Java” as the programming language.

Create Script dialog in Sonargraph

Sonargraph automatically creates a Groovy script with a dummy visitor implementation. As we need to access some Java specific information, we change the first line to IJavaVisistor visitor = javaAccess.getVisitor();
The resulting minimal Groovy script shows the interaction with the visitor and is used to collect all internal types.

Minimal Sonargraph script to list all types

As you can see in the screenshot, the script can be compiled and executed within the application. Apart from simply listing elements in the result, issues and metrics can be created. Textual output can be created by using “println” statements, which is also great for debugging the script. Having the immediate feedback of the results, makes the development of the script within the application really easy.

This minimal script is a nice starting point. Of course, there are more methods to override to examine namespaces, methods, fields, etc.. Check the JavaDoc of the Script API which is included in the application’s context help and online at https://eclipse.hello2morrow.com/doc/standalone/scriptApi/index.html. The code of the full Groovy cript that is shown next is hopefully pretty self-explanatory. Apart from detecting the different singleton types, it also creates a warning, if the instance field is not final (line 40) and if the constructor is not private (line 80). The script is available within the Java quality model in Sonargraph 9.9.1 and higher. Please note that this script is not perfect, for example, it considers only top-level types.

Script Source Code

  1. //Script identifies the following as singletons:
  2. //1. Enum with a single field
  3. //2. Types with a single public static instance field of the same type
  4. //3. Types with a static instance field of the same type and a static method that references this field and has the same return type
  5.  
  6. //It creates warnings for 
  7. //1. Singleton instance fields that are not final
  8. //2. Singletons that have non-private constructor
  9.  
  10. NodeAccess singletons = null;
  11. IJavaVisitor v = javaAccess.createVisitor()
  12.  
  13. v.onType
  14. {
  15.     TypeAccess type ->
  16.     if(type.external || type.excluded || type.interface || type.abstract)
  17.     {
  18.         return
  19.     }
  20.  
  21.     if (type.enum && type.fields.size() == 1)
  22.     {
  23.         singletons = addAsSingleton(type, singletons, true)
  24.         return
  25.     }
  26.  
  27.     def hasOnlyPrivateConstructors = hasOnlyPrivateConstructors(type)
  28.     def staticMembers = getStaticMemberVariablesOfOwnType(type)
  29.     if (staticMembers.empty)
  30.     {
  31.         return
  32.     }
  33.  
  34.     if (staticMembers.size() == 1)
  35.     {
  36.         def onlyMember = staticMembers[0]
  37.         if (onlyMember.static && onlyMember.public)
  38.         {
  39.             singletons = addAsSingleton(type, singletons, hasOnlyPrivateConstructors)
  40.             addWarningIfInstanceFieldNotFinal(onlyMember)
  41.  
  42.             //Continue visiting...
  43.             v.visitChildren(type)
  44.             return
  45.         }
  46.     }
  47.  
  48.     //Check if used in static methods and their return type matches.
  49.     //This is not exact but a good enough heuristic.
  50.     def areStaticMembersReferencedInMethods = false
  51.     staticMembers.each
  52.     {
  53.         if (memberIsUsedInStaticMethod(type, it))
  54.         {
  55.             areStaticMembersReferencedInMethods = true
  56.         }
  57.     }
  58.     if (areStaticMembersReferencedInMethods)
  59.     {
  60.         singletons = addAsSingleton(type, singletons, hasOnlyPrivateConstructors);
  61.     }
  62.  
  63.     //Continue visiting...
  64.     v.visitChildren(type)
  65. }
  66. coreAccess.visitModel(v)
  67.  
  68. //helper method to add info to result
  69. def addAsSingleton(TypeAccess type, Object singletons, boolean hasOnlyPrivateConstructors)
  70. {
  71.     //Add the info to the result
  72.     result.addElement(type)
  73.     if (singletons == null)
  74.     {
  75.         singletons = result.addNode("Singletons");
  76.     }
  77.     result.addNode(singletons, type)
  78.     if (!hasOnlyPrivateConstructors)
  79.     {
  80.         result.addWarningIssue(type, "Degenerate Singleton", "Class has been identified as singleton, but constructor is not private!");
  81.     }
  82.     return singletons
  83. }
  84.  
  85. def getStaticMemberVariablesOfOwnType(TypeAccess type)
  86. {
  87.     def staticMembers = type.fields.findAll
  88.     {
  89.         JavaFieldAccess field ->
  90.         if (!field.static)
  91.         {
  92.             return false;
  93.         }
  94.  
  95.         def outgoing = field.getOutgoingDependencies(Aggregator.TYPE, false)
  96.         if (outgoing.size > 1)
  97.         {
  98.             //more references indicate usage in Collections
  99.             return false;
  100.         }
  101.  
  102.         List ownTypeDependency = outgoing.findAll
  103.         {
  104.             AggregatedDependencyAccess dependency ->
  105.             return dependency.to == type
  106.         }
  107.         return ownTypeDependency.size() == 1
  108.     }
  109.     return staticMembers
  110. }
  111.  
  112. //We cannot check on return usage, but simply check for read-access in static methods as a heuristic.
  113. def memberIsUsedInStaticMethod(TypeAccess type, FieldAccess field)
  114. {
  115.     def referencingMethods = field.getReferencingElements(Aggregator.METHOD,
  116.         true,
  117.         JavaDependencyKind.READ_FIELD);
  118.     def staticMethods = referencingMethods.findAll
  119.     {
  120.         return it.static && it.parent == type
  121.     }
  122.     return !staticMethods.empty
  123. }
  124.  
  125. def hasOnlyPrivateConstructors(TypeAccess type)
  126. {
  127.     def nonPrivateConstructors = type.methods.findAll
  128.     {
  129.         it.constructor && !it.private
  130.     }
  131.     return nonPrivateConstructors.empty
  132. }
  133.  
  134. def addWarningIfInstanceFieldNotFinal(FieldAccess field)
  135. {
  136.     if (!field.final)
  137.     {
  138.         result.addWarningIssue(field, "Singleton instance field should be final", "Field must be final")
  139.     }
  140. }

Dealing with the Result

Detecting patterns via the Groovy script once looks interesting, but now what? What shall be done with this info? The first thing I did was to look through the reported issues for our code base and fixed the programming errors, i.e. making a few instance fields final and add private constructors where they were missing. You can of course extend the script and check if those detected Singletons contain private non-final member variables and the classes serve as global data in disguise. You could also add the script to the automatically executed scripts in Sonargraph and check for those issues during the CI-build, let the build fail (if that’s what you want). And with a little adjustment of the script you could also track the progress of migrating the existing Singletons to the recommended single-element enum approach by creating metrics via the Script API as explained in the Sonargraph User Manual [6].

Summary

This blog post illustrated the capability to extend the Sonargraph analysis with a custom Groovy script to detect the implementations of the Singleton pattern in a code base. The analysis can also be used during development via the IDE plugins or during the CI build. More information about those integrations are available in our user manuals that are contained in the installations or also online [6][7]. Feedback is always welcome and more scripts are soon to come that help implementing the recommendations contained in “Effective Java”.

References

[1] “Gang of Four”, http://wiki.c2.com/?GangOfFour
[2] “Effective Java – Third Edition” by Joshua Bloch, 2018, Addison-Wesley
[3] “Patterns I Hate #1: Singleton” by Alex Miller, 2007, http://web.archive.org/web/20120603233658/http://tech.puredanger.com/2007/07/03/pattern-hate-singleton
[4] “On design patterns, when should I use the Singleton?”, https://stackoverflow.com/questions/228164/on-design-patterns-when-should-i-use-the-singleton
[5] Sonargraph Script API: https://eclipse.hello2morrow.com/doc/standalone/scriptApi/index.html
[6] Sonargraph User Manual, “Chapter 10.  Extending the Static Analysis”, https://eclipse.hello2morrow.com/doc/standalone/content/extending_static_analysis.html
[7] SonargraphBuild User Manual, http://eclipse.hello2morrow.com/doc/build/content/index.html

Leave a Reply

Your email address will not be published. Required fields are marked *