Refactoring constraints refer to pre or postconditions that are checked before and after a refactoring is performed. The constraints mechanism is part of a refactoring meta- model which is implemented directly in Java as part of our tooling. The class diagram of constraints is shown in figure 4.2. Each constraint has three methods: check,
getName, and isGraphLevel. The check method returns true if a preconditions is
fulfilled and false otherwise. ThegetNamemethod returns the name of a precondition
andisGraphLeveldetermines whether the precondition can be checked on the graph
or code level.
Preconditions
There are three preconditions that are checked before the move refactoring is per- formed.
<<interface>> Constraint check(c: Candidate):boolean getName():String isGraphLevel():boolean <<interface>> Precondition <<interface>> Postcondition InstanceCount Compilation Rename Blacklist Accessibility
Figure 4.2:Class Diagram of Pre and Postconditions
4.4.2.1 Rename Precondition
When we move a class from one package to another, it is important to check that the target package should not contain a class with the same name as that of the class to be moved. If a class with the same name exists, the move refactoring is skipped. This precondition is checked on the graph level.
Let C be the set of all classes in a package P. Let name(classToMove) be the name of class to be moved to the package P. Then we define the rename precondition as follows:
@c∈C|name(classToMove)=name(c)
4.4.2.2 Blacklist Precondition
Our algorithm automatically performs the move refactoring in a program. However, in some cases a developer may want that certain classes should not be moved. In that case, we can manually specify a list of fully qualified class names in the Eclipse preferences section of CARE plugin. This is a graph level precondition. This precondition checks whether a class to be moved to another package is blacklisted or not. If a class’ name
exists in the blacklisted classes, the move refactoring is not performed.
Let B be the set of blacklisted classes and let name(classToMove) be the name of the class to be moved to another package. The blacklist precondition is defined as follows:
@b∈B|name(classToMove)=name(b)
4.4.2.3 Accessibility Precondition
Accessibility modifiers establish rules for information hiding within Java programs. There are four types of accessibility modifiers in Java: public, private, protected, and default (no modifier). These modifiers can be applied on two levels:
• Top level (classes): public or default.
• Member level (fields, methods, inner classes): public, private, protected, and
default.
The access level of a class’ artefacts determines whether other classes can access fields or methods of this class or not. The purpose of these modifiers is to support encapsulation. The accessibility precondition checks whether a class to be moved requires changes in the accessibility level of this class or other classes. Consider the example in listing 4.1. In this example, if we try to move the classAoutside of the packagea, we need five change accessibility refactorings. This includes changing the access modifier of class AandCto public. We also need to change the access modifiers of the fieldA.cObject, the methodA.m()andC.m()to public.
We used the Dependency Finder (Tessier, 2010) to extract the accessibility information of a class. For every move candidate class, we compute all non-public incoming and outgoing references of the class within the original package. In listing 4.1 the class A has three incoming references (from class B) at the class, field and method level. There are two outgoing references from the class Ato the classC. If the move class refactoring requires changing accessibility modifiers, we skip the refactoring to preserve encapsulation.
Let X be the set of all move candidate classes and let countAccessabilityChanges(class) determine the number of change accessibility refactorings required in order to move the class. Then we define the accessibility precondition as follows:
∀c∈X|countAccessabilityChanges(c)=0
1 package a;
2 class A {
3 protected C cObject = new C () ;
4 void m () { 5 cObject .m () 6 } 7 } 8 package a; 9 class B { 10 void m () { 11 A a = new A () ; 12 a.m () ; 13 a. cObject .m () ; 14 } 15 } 16 package a; 17 class C { 18 void m () { } 19 }
Listing 4.1: Change Accessibility Example
Postconditions
There are two postconditions for the move class refactoring.
4.4.2.4 Instance Count Postcondition
There could be a situation where a move class refactoring may increase the number of instances as compared to the number of instances before performing the refactoring. Figure 4.3 shows an example where the number of instances increase after a refactoring. In this figure, two edges (C3→C1 and C1→C2) have the same score of one. When two
edges have the same score, they are sorted in ascending order according to the fully qualified names of start and end vertex of each edge. Therefore, the edge C3→C1 is
picked as the first high-scored edge. In this case, C3 was moved from packagey to packagez. There was only one SCD instance before refactoring, while after refactoring the number of instances increased to two2. Table 4.1 shows paths that create circular dependencies before and after the refactoring.
Instance Type Packages Paths SCD Before packagey, packagez C3, C1, C2 SCD After packagey, packagez C5, C3, C4
packagey, packagez C5, C3, C1, C2
Table 4.1:Instance Count Before and After Refactoring
If a situation arises where the number of instances increase after a refactoring, we avoid the move refactoring. This postcondition can be easily checked on the graph level, where we compare the instance count before and after the refactoring. Letgbe the graph before the move refactoring and letg0be the graph after refactoring. Then
we define the instance count postcondition as:
instanceCount(g)≥instanceCount(g0)
In the postcondition we perform a refactoring even if it doesn’t remove any instances. In dense dependency graphs it is possible that one edge removal is rerouted through a different path to create another instance. Therefore, the total number of instances do not decrease, however it is possible that the newly created cycle is removed in the next iterations, which would eventually decrease the number of instances. Figure 4.4 shows an example of instance count postcondition. In this figure the total number of SCD instances is 1 before refactoring. After the first refactoring the number of instances remain the same. However, after the second refactoring the number of instances decrease to zero.
4.4.2.5 Compilation Postcondition
The move class refactoring should be safe, i.e. it should not introduce any compilation errors. However, there are some cases where the program compilation fails after performing the move refactoring. These situations are listed in the section 4.6.11. Therefore, once all other pre and postconditions are passed, this last postcondition (on code level) is checked to ensure that the move refactoring executed successfully.