Modularizing Features of Your Legacy Application for Fun & Profit

DZone 's Guide to

Modularizing Features of Your Legacy Application for Fun & Profit

· Java Zone ·
Free Resource

The user wants me to modify/fix a bug in a certain feature, but what exactly are the classes that I should change? Asking yourself this question on a regular basis? Read on!

In this article, I would like to introduce you to a new NetBeans Platform-based tool, named Featureous, and give you an early sneak peek into the new features and a few implementation pieces of the forthcoming version 3.0. More about the tool you can hear at this year's JavaOne, in the sessions "Modularizing Features of Your Legacy Application for Fun and Profit" and "The How-To of Migrating Legacy Applications to Module Systems".

The goal of Featureous is to make it easier to understand how legacy programs implement their user-observable features. Simply put, if you have ever been in one of the following situations, then you already know the motivation behind Featureous:

  • "The user wants me to modify/fix a bug in this feature, but what exactly are the classes that I should change?"
  • "I need to reuse a part of an implementation of an existing feature, but how do I find the relevant classes?"
  • "I have fixed a bug by modifying class Foo, what features could have been affected by this change?"
  • "I want to untangle my features, so that they can be flexibly added and removed from the application. How to split up the code base in order to achieve this?"

 Featureous helps address these questions by extending the NetBeans IDE with a mechanism for tracing the execution of features. The obtained traces can then be measured and investigated from various angles in a number of analytical views:

More about tracing features with Featureous, using its analytical views and the associated research goals can be found on the project's website: http://featureous.org.

The main focus of the recent developments in the project has been to move Featureous beyond being only an analysis tool, to being also a restructuring tool. The holy grail of this is to make it easier for programmers to answer the question "where is feature Foo implemented?". We want to take programmers away from the usual answer along the lines of "uhmm, you know it's complicated... So, there is the class Bar in that package and then a bunch of other classes in some other places..." towards simply saying "it has its own dedicated package". A step we made towards achieving this vision is the "Remodularization workbench" module, which enriches the tool with a bunch of new restructuring-related features. Needless to say, the implementations of these new features are quite interesting (as we will see in a moment).

The primary utility of the remodularization workbench is the ability to automatically re-locate classes among packages, according to a set of design objectives. The set of objectives available at the moment includes the traditional notions of cohesion and coupling, as well as feature-oriented criteria that aim at localizing and untangling features. For the curious, the machinery under the hood, called a "multi-objective grouping genetic algorithm", makes it possible to arbitrarily mix and match the available objectives and to optimize for multiples of them at the same time.

Visual library + NetBeans editor

To allow a programmer to preview structurings proposed by the remodularization workbench, we have used the NetBeans Visual Library API. One of the interesting things we do here is allowing a programmer to drag and drop classes between packages on the diagram, in order to manually adjust the resulting program structure. While the actual restructuring of sources happens only after all the manual adjustments are finished, we keep the programmer updated on the impact of her actions on the quality of source code by presenting continuously-updated metrics of cohesion, coupling, scattering and tangling.

Another interesting thing we do in our visualization is the possibility to zoom in on the UML-like class widgets in order to display their underlying source code. If you've ever heard of Microsoft's Code Canvas, then you surely know what I mean. You can see how this looks like in our tool in the screenshot below.

The message I want to emphasize here is that this feature was extremely easy to create by just combining some of the existing features of the NetBeans Platform, namely the Visual Library and the Editor API. This case is a good example of how the existing infrastructure enabled something that Hal Varian would call "combinatorial innovation" ( http://www.youtube.com/watch?v=hqaA-fgdXEE).

Below, you can find a code snippet that demonstrates the basic principles behind (a) using the LevelOfDetailsWidget, (b) creating a local instance (which will not be shown as a new editor tab in the IDE) of a fully-fledged NetBeans editor, (c) and wrapping it in a ComponentWidget to inter-operate with rest of Visual Library's scene.
public class UMLClassWidget extends Widget{
   Scene scene = ...;
   //Create uml widget
   Widget uml = new LevelOfDetailsWidget(scene, 0.0, 0.1, 3.0, 4);


   //Create an NB editor
   FileObject javaFile = ...;
//Get Document out of FileObject
   DataObject od = DataObject.find(file);
EditorCookie ec = (EditorCookie) od.getCookie(EditorCookie.class);
Document doc = ec.openDocument();
doc.putProperty(Language.class, JavaTokenId.language());
doc.putProperty("mimeType", "text/x-java");

   BaseKit javaKit = BaseKit.getKit(JavaKit.class);
   JEditorPane pane = new JEditorPane();


   EditorUI editorUI = Utilities.getEditorUI(pane);
   JComponent editor = editorUI.getExtComponent();
   Widget compWidget = new ComponentWidget(scene, editor);
   Widget lod = new LevelOfDetailsWidget(scene, 4, 4, 15.0, 15.1);


Restructuring the source code

Another part of the remodularization workbench that greatly benefited from the underlying NetBeans infrastructure are the routines for physical restructuring of Java source code. Here, we have simply reused the existing "move class" refactoring to reallocate classes to their destination packages. As at a certain point we have observed that sometimes import statements of the resulting classes may be wrong(either due to the specific way we call the refactoring or due to its limitations), we have also reused the "fix all imports" action to post-process the resulting source code.

Below, you can find our recipe for invoking these refactorings programmatically. (Please note that in order to get this to compile you will need to make implementation dependencies on some NetBeans modules.)
DataObject javaObject = ...; //Java source file
String targetPkg = ...;
FileObject projectSrcDir = ...;

MoveRefactoring refactoring = new MoveRefactoring(Lookups.fixed(javaObject.getPrimaryFile(), new TreePathHandle[]{}));

URL srcDirUrl = URLMapper.findURL(projectSrcDir, URLMapper.EXTERNAL);
refactoring.setTarget(Lookups.singleton(new URL(srcDirUrl.toExternalForm() + URLEncoder.encode(targetPkg.replace('.', '/'), "utf-8"))));


RefactoringSession rs = RefactoringSession.create("Move class");

// Fix imports and save
FileObject javaFile = javaObject.getPrimaryFile();

Encapsulating the resulting program as NetBeans modules

Finally, we started working on automatic encapsulation of the resulting programs as NetBeans modules. The idea here is to automate achieving what you typically would want to arrive at when migrating to a module system or to the NetBeans RCP. That is, having a core module that contains all the reusable infrastructural and utility classes for features to build upon, and a number of other modules encapsulating individual features.

The fairly dense code below demonstrates how we programmatically (a) create a module suite and configure it, (b) create a module, (c) declare some of its packages as public, (d) add dependencies between modules.
String suiteDir = ...;

//Create suite and setup branding
SuiteProjectGenerator.createSuiteProject(new File(suiteDir), NbPlatform.getDefaultPlatform().getID(), true);
SuiteProject suite = (SuiteProject)ProjectManager.getDefault().findProject(FileUtil.toFileObject(new File(suiteDir)));
SuiteProperties suiteProps = new SuiteProperties(suite, suite.getHelper(), suite.getEvaluator(), SuiteUtils.getSubProjects(suite));

//Create a module in the suite
String moduleDir = ...;
NbModuleProjectGenerator.createSuiteComponentModule(new File(moduleDir), codeBaseID, moduleName, "bundle.conf", "layer.xml", suite.getProjectDirectoryFile(), false, false);
NbModuleProject mod = (NbModuleProject)ProjectManager.getDefault().findProject(FileUtil.toFileObject(new File(moduleDir)));
mod.getPrimaryConfigurationData(); // force creation of config files

//Add public package declaration
ProjectXMLManager xmlMan = ProjectXMLManager.getInstance(mod.getProjectDirectoryFile());
PackageExport[] pes = xmlMan.getPublicPackages();
Set<String> pkgs = new HashSet<String>();
for (PackageExport pe : pes) {

//Add dependency on another module destMod
NbModuleProject destMod = ...;
ModuleDependency dep = new ModuleDependency(destMod.getModuleList().getEntry(destMod.getCodeNameBase()), null, null, true, false);

Summing up, this demonstrates some of the implementation pieces involved in the forthcoming Featureous 3.0. Very soon you will be able try out the new features yourself, browse the complete source code and contribute your own modules and patches to the tool, so stay tuned. The planned release date is September 30th.

Finally, if you will be around at JavaOne, please don't hesitate to drop by and notify us about your impressions, experiences and ideas regarding Featureous.



Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}