Our work focuses on the automatic update of Java code in response to changes in library interfaces. Our approach is based on refactoring rules that are specified by the library author and later applied by a developer on client code. This approach turns out to be very successful in practice. Although many researchers have focused on the problem of automatic migration to a new API and several approaches to the problem have been suggested, there is currently no widely used tool for automatic migration and research is still active in this area.
The Java programming language is one of the world’s most popular programming languages. It was introduced in 1996 as a simple programming language based on C syntax and it has been evolving ever since. Over the last 15 years, it has evolved to a mature programming language with advanced features such as parametric polymorphism.
From the beginning, the Java programming language has had standard library classes (Java API) that have been evolving along with the language. In the course of the evolution many new classes and methods have been introduced and many have been deprecated. For example, the enable() method in the java.awt.Component class was deprecated and replaced by setEnable(boolean b) in Java 1.1. This approach to API evolution is motivated by backward compatibility: A source code that compiled in Java 1.0 should compile in Java 1.1 as well. The same approach to API evolution is commonly used in Java libraries. For example, Simple API for XML Processing (SAX) version 2 deprecated six classes from SAX version 1 and introduced new classes to replace them. Although this is profitable for source compatibility, it has also two significant disadvantages: (a) API developers have to maintain several APIs in parallel and (b) deprecated methods inhibit API evolution because API developers are restricted by them.
In many cases, deprecated and new classes and methods are similar because the change in API was caused by some kind of refactoring, ie a structural transformation that does not change code behaviour. The fact that many API changes are caused by refactoring provides a good incentive for the development of tools that are able to apply the same refactorings on client code. Such tools can facilitate migration to a new version of the library. The reasons for migration may vary and might include, for example, bug fixes or performance improvements. Upgrade to a new version of the library involves changes in source code that are currently usually done manually. This is boring, tedious and error-prone.
Our research is focused on tools that are able to adapt Java source code to a new library interface provided that the changes in the library interface were caused by refactorings. Such tools can reduce the workload for programmers. In addition, they can facilitate maintenance of API because deprecated classes and methods may be safely removed if clients can migrate to new classes and methods automatically.
Figure 1: Workflow in RefactoringNG and JUpgrade.
We designed two tools, RefactoringNG and JUpgrade, that can apply specified transformations on Java source code. As well as many other transformation tools, they do not work directly with source code but apply transformations on the abstract syntax trees (ASTs). The ASTs are built and attributed by the Java compiler and we access them through the Compiler Tree API. Transformations are applied in three steps: first, we let the compiler build and attribute the ASTs; second, we apply transformations (this typically modifies some ASTs); third, we convert the modified ASTs to source code. Transformations that can be applied vary between RefactoringNG and JUpgrade. RefactoringNG can only rewrite ASTs to other ASTs. Each AST transformation here is described by a rule that consists of two ASTs: the pattern tree and the rewrite tree. The tool searches for the pattern tree and when found, it rewrites the pattern tree to the rewrite tree. In comparison to other tools, RefactoringNG has available complete syntactic and semantic information.
JUpgrade uses a higher-level approach. We specify refactorings we want to apply to source code and the tool applies them without human intervention. These refactorings can be, for example, "rename method" or "change method signature". Before applying a refactoring, the tool verifies that the refactoring is valid in client context. For example, “rename method” can be applied only if no method with given signature exists in target class. As for API clients, we distinguish between instantiators and extenders. The instantiator instantiates classes and uses classes, interfaces, enums, and annotations. Thus it sees only public members of types. The extender extends classes and extends and implements interfaces. Besides the public members, it sees protected members as well. When migrating from SAX 1 to SAX 2 with JUpgrade, we successfully migrated 43 out of 50 method calls in complete instantiator and 46 out of 50 methods in complete extender.
Faculty of Information Technology, Czech Technical University in Prague