Last updated July 20, 2010 17:34, by Zdenek Tronicek
Feedicon  

API Evolution with RefactoringNG

In this article, we will go through several common API changes and show how RefactoringNG can facilitate adoption of the new API. We assume an application that compiles against some API (component, library, or framework) and for several structural API changes we show how the library authors can help users to adopt a new API by providing the refactoring scripts for RefactoringNG. As soon as we have refactoring scripts, upgrade to a new API version is quick and painless.

Moved method

Let's have the dock method in the Ship class:

 public class Ship {
    public void dock() { ... }
    ...
 }

As part of API evolution, we will move the method to the Harbour class and make it static:

 public class Harbour {
    public static void dock(Ship s) { ... }
    ...
 }

So, the code that calls the dock method on instance of Ship will be refactored to call the dock method on the Harbour class. For example,

 s.dock();

will be replaced with

 Harbour.dock(s);

assuming that s is a variable of the Ship type or any subclass.

In RefactoringNG, this can be accomplished by the following rule:

 MethodInvocation {
    List<Tree> { },
    MemberSelect [identifier: "dock"] {
       Identifier [id: s, instanceof: "navy.Ship"]
    },
    List<Expression> { }
 } ->
 MethodInvocation {
    List<Tree> { },
    MemberSelect [identifier: "dock"] {
       Identifier [name: "Harbour"]
    },
    List<Expression> {
       Identifier [ref: s]
    }
 }


Moved field

Let's assume the DEFAULT_SHIP_CAPACITY field in the Harbour class:

 public class Harbour {
    public static final int DEFAULT_SHIP_CAPACITY = 100;
    ...
 }

We will move the field to the Ship class and rename it:

 public class Ship {
    public static final int DEFAULT_CAPACITY = 100;
    ...
 }

So, we need to replace all occurrences of Harbour.DEFAULT_SHIP_CAPACITY with Ship.DEFAULT_CAPACITY. In RefactoringNG, this can be done by the following rule:

 MemberSelect [identifier: "DEFAULT_SHIP_CAPACITY"] {
    Identifier [elementKind: CLASS, qualifiedName: "navy.Harbour"]
 } ->
 MemberSelect [identifier: "DEFAULT_CAPACITY"] {
    Identifier [name: "Ship"]
 }


Deleted method

Let's assume the Barge class with the start method which must be called at the beginning of work with the barge:

 public class Barge extends Ship {
    public void start() { ... }
    ...
 }

As part of API evolution, we will move the responsibility of this method to the constructor and remove it. So, we can remove all the calls of this method:

 List<Statement> [id: st, minSize: 1, maxSize: *] {
    ExpressionStatement [id: del, pos: *] {
       MethodInvocation {
          List<Tree> { },
          MemberSelect [identifier: "start"] {
             Identifier [instanceof: "navy.Barge"]
          },
          List<Expression> { }
       }
    }
 } ->
 List<Statement> {
    ListItems [ref: st, exclude: del]
 }


Changed argument type

Let's assume the load method in the Ship class:

 public class Ship {
    public void load(Cargo c) { ... }
    ...
 }

We will change the argument type from Cargo to List<Cargo>:

 public class Ship {
    public void load(List<Cargo> c) { ... }
    ...
 }

In RefactoringNG, we first add import of java.util.Collections:

 List<Import> [id: imports]
 ->
 List<Import> {
    ListItems [ref: imports],
    Import [static: false] {
       MemberSelect [identifier: "Collections"] {
          MemberSelect [identifier: "util"] {
             Identifier [name: "java"]
          }
       }
    }
 }

And then we replace a call to the original method with the new method call:

 MethodInvocation {
    List<Tree> { },
    MemberSelect [identifier: "load"] {
       Identifier [id: s, instanceof: "navy.Ship"]
    },
    List<Expression> [id: args] {
       Expression [kind: IDENTIFIER | NEW_CLASS]
    }
 } ->
 MethodInvocation {
    List<Tree> { },
    MemberSelect [identifier: "load"] {
       Identifier [ref: s]
    },
    List<Expression> {
       MethodInvocation {
          List<Tree> { },
          MemberSelect [identifier: "singletonList"] {
             Identifier [name: "Collections"]
          },
          List<Expression> [ref: args]
       }
    }
 }


Extra argument

Let's assume the sail method in the Ship class:

 public class Ship {
    public void sail() { ... }
    ...
 }

As part of API evolution, we will add an argument to this method:

 public class Ship {
    public void sail(int speed) { ... }
    ...
 }

So, we need to replace invocations of the no-argument method with the one-argument one:

 MethodInvocation {
    List<Tree> { },
    MemberSelect [identifier: "sail"] {
       Identifier [id: s, instanceof: "navy.Ship"]
    },
    List<Expression> { }
 } ->
 MethodInvocation {
    List<Tree> { },
    MemberSelect [identifier: "sail"] {
       Identifier [ref: s]
    },
    List<Expression> {
       Literal [kind: INT_LITERAL, value: 42]
    }
 }

If you want to try it, download Demo.zip. The zip file contains four NetBeans projects: original library ShipLibrary1, evolved library ShipLibrary2, original application ShipApp1, and application ShipApp2, that is to be refactored. ShipApp1 compiles against ShipLibrary1 and ShipApp2 will compile against ShipLibrary2 when you apply script 'shiplibrary.rng'.

  • Mysql
  • Glassfish
  • Jruby
  • Rails
  • Nblogo
Terms of Use; Privacy Policy;
© 2010, Oracle Corporation and/or its affiliates
(revision 20120127.ac94057)
 
 
Close
loading
Please Confirm
Close