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:
- Single-element enum as recommended by Joshua Bloch, because you don’t need to worry about serialization issues.
- Type with a public final instance field.
- 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).
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.
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.
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
//Script identifies the following as singletons:
//1. Enum with a single field
//2. Types with a single public static instance field of the same type
//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
//It creates warnings for
//1. Singleton instance fields that are not final
//2. Singletons that have non-private constructor
NodeAccess singletons = null;
IJavaVisitor v = javaAccess.createVisitor()
v.onType
{
TypeAccess type ->
if(type.external || type.excluded || type.interface || type.abstract)
{
return
}
if (type.enum && type.fields.size() == 1)
{
singletons = addAsSingleton(type, singletons, true)
return
}
def hasOnlyPrivateConstructors = hasOnlyPrivateConstructors(type)
def staticMembers = getStaticMemberVariablesOfOwnType(type)
if (staticMembers.empty)
{
return
}
if (staticMembers.size() == 1)
{
def onlyMember = staticMembers[0]
if (onlyMember.static && onlyMember.public)
{
singletons = addAsSingleton(type, singletons, hasOnlyPrivateConstructors)
addWarningIfInstanceFieldNotFinal(onlyMember)
//Continue visiting...
v.visitChildren(type)
return
}
}
//Check if used in static methods and their return type matches.
//This is not exact but a good enough heuristic.
def areStaticMembersReferencedInMethods = false
staticMembers.each
{
if (memberIsUsedInStaticMethod(type, it))
{
areStaticMembersReferencedInMethods = true
}
}
if (areStaticMembersReferencedInMethods)
{
singletons = addAsSingleton(type, singletons, hasOnlyPrivateConstructors);
}
//Continue visiting...
v.visitChildren(type)
}
coreAccess.visitModel(v)
//helper method to add info to result
def addAsSingleton(TypeAccess type, Object singletons, boolean hasOnlyPrivateConstructors)
{
//Add the info to the result
result.addElement(type)
if (singletons == null)
{
singletons = result.addNode("Singletons");
}
result.addNode(singletons, type)
if (!hasOnlyPrivateConstructors)
{
result.addWarningIssue(type, "Degenerate Singleton", "Class has been identified as singleton, but constructor is not private!");
}
return singletons
}
def getStaticMemberVariablesOfOwnType(TypeAccess type)
{
def staticMembers = type.fields.findAll
{
JavaFieldAccess field ->
if (!field.static)
{
return false;
}
def outgoing = field.getOutgoingDependencies(Aggregator.TYPE, false)
if (outgoing.size > 1)
{
//more references indicate usage in Collections
return false;
}
List ownTypeDependency = outgoing.findAll
{
AggregatedDependencyAccess dependency ->
return dependency.to == type
}
return ownTypeDependency.size() == 1
}
return staticMembers
}
//We cannot check on return usage, but simply check for read-access in static methods as a heuristic.
def memberIsUsedInStaticMethod(TypeAccess type, FieldAccess field)
{
def referencingMethods = field.getReferencingElements(Aggregator.METHOD,
true,
JavaDependencyKind.READ_FIELD);
def staticMethods = referencingMethods.findAll
{
return it.static && it.parent == type
}
return !staticMethods.empty
}
def hasOnlyPrivateConstructors(TypeAccess type)
{
def nonPrivateConstructors = type.methods.findAll
{
it.constructor && !it.private
}
return nonPrivateConstructors.empty
}
def addWarningIfInstanceFieldNotFinal(FieldAccess field)
{
if (!field.final)
{
result.addWarningIssue(field, "Singleton instance field should be final", "Field must be final")
}
}
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