automated refactoring of legacy java software to default methods Raffi Khatchadourian1 Hidehiko Masuhara2 International Conference on Software Engineering, 2017 1 Computer Science, Hunter College & the Graduate Center, City University of New York, USA 2 Mathematical and Computing Science, Tokyo Institute of Technology, Japan
motivation
interfaces are traditionally lists of method declarations ∙ Traditionally, an interface is a Java type that lists method declarations. interface Collection<E> { int size(); void add(E elem); boolean isEmpty(); int capacity(); abstract boolean atCapacity();} 2
interfaces are traditionally lists of method declarations ∙ Traditionally, an interface is a Java type that lists method declarations. ∙ Clients are guaranteed that concrete interface implementers provide implementations for all listed methods. interface Collection<E> { int size(); void add(E elem); boolean isEmpty(); int capacity(); abstract boolean atCapacity();} 2
some interface methods are optional ∙ Interface methods can be listed as optional operations. interface Collection<E> { // ... void add(E elem); /* optional */ }} 3
some interface methods are optional ∙ Interface methods can be listed as optional operations. ∙ Implementers may choose to support them or not. interface Collection<E> { // ... void add(E elem); /* optional */ }} class ImmutableList<E> implements Collection<E> { // ... } 3
some interface methods are optional ∙ Interface methods can be listed as optional operations. ∙ Implementers may choose to support them or not. ∙ If operations are unsupported, they conventionally throw an UnsupportedOperationException. interface Collection<E> { // ... void add(E elem); /* optional */ }} class ImmutableList<E> implements Collection<E> { // ... @Override public void add(E elem) { throw new UnsupportedOperationException();}} 3
skeletal implementation classes help implement interfaces ∙ The skeletal implementation design pattern [Bloch, 2008] is used to make implementing interfaces easier. 4
skeletal implementation classes help implement interfaces ∙ The skeletal implementation design pattern [Bloch, 2008] is used to make implementing interfaces easier. ∙ Abstract skeletal implementation class provides partial implementations. abstract class AbstractImmutableList<E> implements Collection<E> { @Override public void add(E elem) { throw new UnsupportedOperationException();}} 4
skeletal implementation classes help implement interfaces ∙ The skeletal implementation design pattern [Bloch, 2008] is used to make implementing interfaces easier. ∙ Abstract skeletal implementation class provides partial implementations. ∙ Implementers extend the skeletal implementation class rather than directly implementing the interface. abstract class AbstractImmutableList<E> implements Collection<E> { @Override public void add(E elem) { throw new UnsupportedOperationException();}} class ImmutableList<E> extends AbstractImmutableList<E>{ // ... @Override public void add(E elem) { throw new UnsupportedOperationException();}}} 4
the skeletal implementation pattern has several drawbacks The skeletal implementation pattern has several drawbacks: Inheritance ImmutableList cannot: ∙ Subclass another class. ∙ Inherit skeletal implementations split over multiple classes [Horstmann, 2014]. ∙ Inherit skeletal implementations for multiple interfaces. Modularity No syntactic path between Collection and AbstractCollection (may require global analysis [Khatchadourian et al., 2016]). Bloat ∙ Separate classes can complicate libraries, making maintenance difficult. ∙ Method declarations needed in both interface and abstract class. 5
java 8 default methods can replace skeletal implementations ∙ Java 8 enhanced interfaces allow both method declarations and definitions. interface Collection<E> { default void add(E elem) { // optional. throw new UnsupportedOperationException();}} 6
java 8 default methods can replace skeletal implementations ∙ Java 8 enhanced interfaces allow both method declarations and definitions. ∙ Implementers inherit the (default) implementation if none provided. interface Collection<E> { default void add(E elem) { // optional. throw new UnsupportedOperationException();}} class ImmutableList<E> implements Collection<E> {} 6
java 8 default methods can replace skeletal implementations ∙ Java 8 enhanced interfaces allow both method declarations and definitions. ∙ Implementers inherit the (default) implementation if none provided. ∙ Original motivation to facilitate interface evolution. interface Collection<E> { default void add(E elem) { // optional. throw new UnsupportedOperationException();}} class ImmutableList<E> implements Collection<E> {} 6
java 8 default methods can replace skeletal implementations ∙ Java 8 enhanced interfaces allow both method declarations and definitions. ∙ Implementers inherit the (default) implementation if none provided. ∙ Original motivation to facilitate interface evolution. ∙ Can also be used as a replacement of the skeletal implementation pattern [Goetz, 2011]. interface Collection<E> { default void add(E elem) { // optional. throw new UnsupportedOperationException();}} class ImmutableList<E> implements Collection<E> {} abstract class AbstractImmutableList<E> implements Collection<E> { @Override public void add(E elem) { throw new UnsupportedOperationException();}} 6
default methods can compensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
default methods can compensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
default methods can compensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
default methods can compensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
default methods can compensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
default methods can compensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
default methods can compensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
migration to interfaces as default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
migration to interfaces as default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
migration to interfaces as default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
migration to interfaces as default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
migration to interfaces as default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
migration to interfaces as default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
migration to interfaces as default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
migration to interfaces as default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
pull up method refactoring not directly applicable ∙ Pull Up Method refactoring [Fowler, 1999; Tip et al., 2011] safely moves methods from a subclass into a super class. ∙ Goal is solely to reduce redundant code. ∙ Java has multiple interface inheritance. ∙ More complicated type hierarchy involving interfaces. ∙ “Competition” with classes (tie-breaking). ∙ Differences between class method headers (sources) and corresponding interface method declarations (targets). 9
move original method to super class does not deal with bodies ∙ “Move Original Method to Super Class” law [Borba et al., 2004] expresses transformational semantic equivalence. ∙ In our case, no method declarations are being moved but rather bodies. 10
our contribution
target methods with multiple source methods interface Collection<E> { boolean isEmpty();} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} 12
target methods with multiple source methods interface Collection<E> { boolean isEmpty();} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} ∙ May not have a one-to-one correspondence between source and target methods. 12
target methods with multiple source methods interface Collection<E> { boolean isEmpty();} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} ∙ May not have a one-to-one correspondence between source and target methods. ∙ Migrating any of the source methods passing preconditions would be safe. 12
target methods with multiple source methods interface Collection<E> { boolean isEmpty();} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} ∙ May not have a one-to-one correspondence between source and target methods. ∙ Migrating any of the source methods passing preconditions would be safe. ∙ Choose the largest number of “equivalent” source methods. 12
target methods with multiple source methods interface Collection<E> { default boolean isEmpty() {return this.size() == 0;}} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} ∙ May not have a one-to-one correspondence between source and target methods. ∙ Migrating any of the source methods passing preconditions would be safe. ∙ Choose the largest number of “equivalent” source methods. 12
interfaces cannot declare instance fields interface Collection<E> { int size();} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? 13
interfaces cannot declare instance fields interface Collection<E> { default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? 13
interfaces cannot declare instance fields interface Collection<E> { default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? 13
interfaces cannot declare instance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? 13
interfaces cannot declare instance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? ∙ Interfaces cannot declare instance fields. 13
interfaces cannot declare instance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? ∙ Interfaces cannot declare instance fields. 13
interfaces cannot declare instance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? ∙ Interfaces cannot declare instance fields. Question In general, how can we guarantee that migration results in a type-correct transformation? 13
interfaces cannot declare instance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? ∙ Interfaces cannot declare instance fields. Question In general, how can we guarantee that migration results in a type-correct transformation? Answer Use type constraints [Palsberg and Schwartzbach, 1994; Tip et al., 2011] to check refactoring preconditions. 13
using type constraints as refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. 14
using type constraints as refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. ∙ A complete program is type-correct if all constraints implied by all program elements hold. 14
using type constraints as refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. ∙ A complete program is type-correct if all constraints implied by all program elements hold. program construct implied type constraint(s) access E.f to field F [E.f] ≜ [F] (1) [E] ≤ Decl(F) (2) 14
using type constraints as refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. ∙ A complete program is type-correct if all constraints implied by all program elements hold. program construct implied type constraint(s) access E.f to field F [E.f] ≜ [F] (1) [E] ≤ Decl(F) (2) Migrating size() to Collection would imply [this] = Collection. interface Collection<E> { default int size() {return this.size;}} 14
using type constraints as refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. ∙ A complete program is type-correct if all constraints implied by all program elements hold. program construct implied type constraint(s) access E.f to field F [E.f] ≜ [F] (1) [E] ≤ Decl(F) (2) Migrating size() to Collection would imply [this] = Collection. interface Collection<E> { default int size() {return this.size;}} This violates constraint (2) that [this] ≤ [AbsList]. abstract class AbsList<E> implements Collection<E> { @Override public int size() {return this.size;}} 14
new type constraints, definitions, and semantics preservation ∙ Extend [Tip et al., 2011] with new constraints, new definitions, and semantics preservation for default methods. ∙ See paper for more details. 15
preserving semantics in light of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { void removeLast(); void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsQueue. 16
preserving semantics in light of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { void removeLast(); void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsQueue. ∙ Can we migrate removeLast() from AbsQueue to Queue? 16
preserving semantics in light of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsQueue. ∙ Can we migrate removeLast() from AbsQueue to Queue? 16
preserving semantics in light of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to Queue? ∙ Can we migrate removeLast() from AbsQueue to Queue? 16
preserving semantics in light of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsList. ∙ Can we migrate removeLast() from AbsQueue to Queue? ∙ Now dispatches to AbsList as classes take precedence! 16
preserving semantics in light of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsList. ∙ Can we migrate removeLast() from AbsQueue to Queue? ∙ Now dispatches to AbsList as classes take precedence! ∙ Queue loses “tie” with AbsList. 16
preserving semantics in light of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsQueue. ∙ Can we migrate removeLast() from AbsQueue to Queue? ∙ Now dispatches to AbsList as classes take precedence! ∙ Queue loses “tie” with AbsList. ∙ Disallow methods that override in both classes and interfaces. 16
eclipse plug-in and case study subject KL KM cnds dflts fps δ -δ tm (s) ArtOfIllusion 118 6.94 16 1 34 1 0 3.65 Azureus 599 3.98 747 116 1366 31 2 61.83 Colt 36 3.77 69 4 140 3 0 6.76 elasticsearch 585 47.87 339 69 644 21 4 83.30 Java8 291 30.99 299 93 775 25 10 64.66 JavaPush 6 0.77 1 0 4 0 0 1.02 JGraph 13 1.47 16 2 21 1 0 3.12 JHotDraw 32 3.60 181 46 282 8 0 7.75 JUnit 26 3.58 9 0 25 0 0 0.79 MWDumper 5 0.40 11 0 24 0 0 0.29 osgi 18 1.81 13 3 11 2 0 0.76 rdp4j 2 0.26 10 8 2 1 0 1.10 spring 506 53.51 776 150 1459 50 13 91.68 Tomcat 176 16.15 233 31 399 13 0 13.81 verbose 4 0.55 1 0 1 0 0 0.55 VietPad 11 0.58 15 0 26 0 0 0.36 Violet 27 2.06 104 40 102 5 1 3.54 Wezzle2D 35 2.18 87 13 181 5 0 4.26 ZKoss 185 15.95 394 76 684 0 0 33.95 Totals: 2677 232.2 3321 652 6180 166 30 383.17 ∙ Implemented as an open source Eclipse plug-in. ∙ Evaluated on 19 Java programs of varying size and domain. ∙ Automatically migrated 19.63% (column dflts) of candidate despite conservatism. ∙ Running time (column tm (s)) averaged ∼0.144 secs/KLOC. 17
refactoring precondition failure distribution ∙ Field and method inaccessibility from the destination interface accounted for largest number of errors. ∙ Next largest failure due to instance field accesses (failures of constraint (2)). 18
preliminary pull request study ∙ Submitted 19 pull requests to Java projects on GitHub. ∙ 4 were successfully merged, 5 are still open, and 10 were closed without merging. ∙ Merged projects totaled 163 watches, 1071 stars, and 180 forks. ∙ Projects rejecting requests citing reasons such as: ∙ They had not yet moved or were in the process of moving to Java 8. ∙ Needed to support older Java clients (Android). 19
summary ∙ Efficient, fully-automated, semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. 20
summary ∙ Efficient, fully-automated, semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. 20
summary ∙ Efficient, fully-automated, semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. ∙ Tool scales and refactored 19.63% of methods possibly participating in the pattern. 20
summary ∙ Efficient, fully-automated, semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. ∙ Tool scales and refactored 19.63% of methods possibly participating in the pattern. ∙ 4 pull requests merged into GitHub repositories, including large, widely used frameworks from reputable organizations. 20
summary ∙ Efficient, fully-automated, semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. ∙ Tool scales and refactored 19.63% of methods possibly participating in the pattern. ∙ 4 pull requests merged into GitHub repositories, including large, widely used frameworks from reputable organizations. ∙ Studies highlight pattern usage and gives possible insight to language designers on construct applicability to existing software. 20
summary ∙ Efficient, fully-automated, semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. ∙ Tool scales and refactored 19.63% of methods possibly participating in the pattern. ∙ 4 pull requests merged into GitHub repositories, including large, widely used frameworks from reputable organizations. ∙ Studies highlight pattern usage and gives possible insight to language designers on construct applicability to existing software. ∙ Graduate positions available! http://bit.ly/cunygrad 20
precondition failures breakdown # Precondition Fails P1 MethodContainsInconsistentParameterAnnotations 1 P2 MethodContainsCallToProtectedObjectMethod 1 P3 TypeVariableNotAvailable 10 P4 DestinationInterfaceIsFunctional 17 P5 TargetMethodHasMultipleSourceMethods 19 P6 MethodContainsIncompatibleParameterTypeParameters 42 P7 NoMethodsWithMultipleCandidateDestinations 53 P8 TypeNotAccessible 64 P9 SourceMethodImplementsMultipleMethods 72 P10 SourceMethodProvidesImplementationsForMultipleMethods 79 P11 MethodContainsTypeIncompatibleThisReference 79 P12 IncompatibleMethodReturnTypes 104 P13 ExceptionTypeMismatch 105 P14 MethodContainsSuperReference 147 P15 SourceMethodOverridesClassMethod 258 P16 AnnotationMismatch 305 P17 SourceMethodAccessesInstanceField 463 P18 MethodNotAccessible 1,679 P19 FieldNotAccessible 2,565 Table: Precondition failures. 21
for further reading Joshua Bloch. Effective Java. Prentice Hall, 2008. Paulo Borba, Augusto Sampaio, Ana Cavalcanti, and Márcio Cornélio. Algebraic reasoning for object-oriented programming. Science of Computer Programming, 52 (1-3):53–100, August 2004. ISSN 0167-6423. doi: 10.1016/j.scico.2004.03.003. Martin Fowler. Refactoring: Improving the Design of Existing Code. Addison-Wesley Professional, 1999. Brian Goetz. Interface evolution via virtual extensions methods. Technical report, Oracle Corporation, June 2011. URL http://cr.openjdk.java.net/ ~briangoetz/lambda/Defender%20Methods%20v4.pdf. Cay S. Horstmann. Java SE 8 for the Really Impatient. Addison-Wesley Professional, 2014. Raffi Khatchadourian, Olivia Moore, and Hidehiko Masuhara. Towards improving interface modularity in legacy java software through automated refactoring. In Companion Proceedings of the 15th International Conference on Modularity, MODULARITY Companion 2016, pages 104–106, New York, NY, USA, 2016. ACM. ISBN 978-1-4503-4033-5. doi: 10.1145/2892664.2892681. Jens Palsberg and Michael I. Schwartzbach. Object-oriented type systems. John Wiley and Sons Ltd., 1994. ISBN 0-471-94128-X. Frank Tip, Robert M. Fuhrer, Adam Kieżun, Michael D. Ernst, Ittai Balaban, and Bjorn De Sutter. Refactoring using type constraints. ACM Transactions on Programming Languages and Systems, 33(3):9:1–9:47, May 2011. ISSN 0164-0925. doi: 10.1145/1961204.1961205. 22

Automated Refactoring of Legacy Java Software to Default Methods Talk at ICSE 2017

  • 1.
    automated refactoring oflegacy java software to default methods Raffi Khatchadourian1 Hidehiko Masuhara2 International Conference on Software Engineering, 2017 1 Computer Science, Hunter College & the Graduate Center, City University of New York, USA 2 Mathematical and Computing Science, Tokyo Institute of Technology, Japan
  • 2.
  • 3.
    interfaces are traditionallylists of method declarations ∙ Traditionally, an interface is a Java type that lists method declarations. interface Collection<E> { int size(); void add(E elem); boolean isEmpty(); int capacity(); abstract boolean atCapacity();} 2
  • 4.
    interfaces are traditionallylists of method declarations ∙ Traditionally, an interface is a Java type that lists method declarations. ∙ Clients are guaranteed that concrete interface implementers provide implementations for all listed methods. interface Collection<E> { int size(); void add(E elem); boolean isEmpty(); int capacity(); abstract boolean atCapacity();} 2
  • 5.
    some interface methodsare optional ∙ Interface methods can be listed as optional operations. interface Collection<E> { // ... void add(E elem); /* optional */ }} 3
  • 6.
    some interface methodsare optional ∙ Interface methods can be listed as optional operations. ∙ Implementers may choose to support them or not. interface Collection<E> { // ... void add(E elem); /* optional */ }} class ImmutableList<E> implements Collection<E> { // ... } 3
  • 7.
    some interface methodsare optional ∙ Interface methods can be listed as optional operations. ∙ Implementers may choose to support them or not. ∙ If operations are unsupported, they conventionally throw an UnsupportedOperationException. interface Collection<E> { // ... void add(E elem); /* optional */ }} class ImmutableList<E> implements Collection<E> { // ... @Override public void add(E elem) { throw new UnsupportedOperationException();}} 3
  • 8.
    skeletal implementation classeshelp implement interfaces ∙ The skeletal implementation design pattern [Bloch, 2008] is used to make implementing interfaces easier. 4
  • 9.
    skeletal implementation classeshelp implement interfaces ∙ The skeletal implementation design pattern [Bloch, 2008] is used to make implementing interfaces easier. ∙ Abstract skeletal implementation class provides partial implementations. abstract class AbstractImmutableList<E> implements Collection<E> { @Override public void add(E elem) { throw new UnsupportedOperationException();}} 4
  • 10.
    skeletal implementation classeshelp implement interfaces ∙ The skeletal implementation design pattern [Bloch, 2008] is used to make implementing interfaces easier. ∙ Abstract skeletal implementation class provides partial implementations. ∙ Implementers extend the skeletal implementation class rather than directly implementing the interface. abstract class AbstractImmutableList<E> implements Collection<E> { @Override public void add(E elem) { throw new UnsupportedOperationException();}} class ImmutableList<E> extends AbstractImmutableList<E>{ // ... @Override public void add(E elem) { throw new UnsupportedOperationException();}}} 4
  • 11.
    the skeletal implementationpattern has several drawbacks The skeletal implementation pattern has several drawbacks: Inheritance ImmutableList cannot: ∙ Subclass another class. ∙ Inherit skeletal implementations split over multiple classes [Horstmann, 2014]. ∙ Inherit skeletal implementations for multiple interfaces. Modularity No syntactic path between Collection and AbstractCollection (may require global analysis [Khatchadourian et al., 2016]). Bloat ∙ Separate classes can complicate libraries, making maintenance difficult. ∙ Method declarations needed in both interface and abstract class. 5
  • 12.
    java 8 defaultmethods can replace skeletal implementations ∙ Java 8 enhanced interfaces allow both method declarations and definitions. interface Collection<E> { default void add(E elem) { // optional. throw new UnsupportedOperationException();}} 6
  • 13.
    java 8 defaultmethods can replace skeletal implementations ∙ Java 8 enhanced interfaces allow both method declarations and definitions. ∙ Implementers inherit the (default) implementation if none provided. interface Collection<E> { default void add(E elem) { // optional. throw new UnsupportedOperationException();}} class ImmutableList<E> implements Collection<E> {} 6
  • 14.
    java 8 defaultmethods can replace skeletal implementations ∙ Java 8 enhanced interfaces allow both method declarations and definitions. ∙ Implementers inherit the (default) implementation if none provided. ∙ Original motivation to facilitate interface evolution. interface Collection<E> { default void add(E elem) { // optional. throw new UnsupportedOperationException();}} class ImmutableList<E> implements Collection<E> {} 6
  • 15.
    java 8 defaultmethods can replace skeletal implementations ∙ Java 8 enhanced interfaces allow both method declarations and definitions. ∙ Implementers inherit the (default) implementation if none provided. ∙ Original motivation to facilitate interface evolution. ∙ Can also be used as a replacement of the skeletal implementation pattern [Goetz, 2011]. interface Collection<E> { default void add(E elem) { // optional. throw new UnsupportedOperationException();}} class ImmutableList<E> implements Collection<E> {} abstract class AbstractImmutableList<E> implements Collection<E> { @Override public void add(E elem) { throw new UnsupportedOperationException();}} 6
  • 16.
    default methods cancompensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
  • 17.
    default methods cancompensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
  • 18.
    default methods cancompensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
  • 19.
    default methods cancompensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
  • 20.
    default methods cancompensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
  • 21.
    default methods cancompensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
  • 22.
    default methods cancompensate for drawbacks Using default methods: Inheritance ImmutableList can: ∙ Subclass another class. ∙ Inherit centralized default methods for an interface. ∙ Inherit default methods for each interface. Modularity No need to find default implementations (does not require global analysis). Bloat ∙ No separate classes to complicate libraries, making maintenance easier. ∙ No method declarations needed in both interface and abstract class. 7
  • 23.
    migration to interfacesas default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
  • 24.
    migration to interfacesas default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
  • 25.
    migration to interfacesas default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
  • 26.
    migration to interfacesas default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
  • 27.
    migration to interfacesas default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
  • 28.
    migration to interfacesas default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
  • 29.
    migration to interfacesas default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
  • 30.
    migration to interfacesas default methods can be difficult Migrating legacy code using the skeletal implementation pattern to instead use default methods can require significant manual effort, especially in large and complex projects. ∙ Skeletal implementation pattern is ubiquitous, particularly in frameworks. ∙ Subtle language and semantic interface restrictions. ∙ Requires: ∙ Preserving type-correctness by analyzing possibly complex type hierarchies. ∙ Resolving issues arising from multiple inheritance. ∙ Reconciling possibly minute differences between class and interface methods. ∙ Ensuring tie-breakers with overriding class methods do not alter semantics. 8
  • 31.
    pull up methodrefactoring not directly applicable ∙ Pull Up Method refactoring [Fowler, 1999; Tip et al., 2011] safely moves methods from a subclass into a super class. ∙ Goal is solely to reduce redundant code. ∙ Java has multiple interface inheritance. ∙ More complicated type hierarchy involving interfaces. ∙ “Competition” with classes (tie-breaking). ∙ Differences between class method headers (sources) and corresponding interface method declarations (targets). 9
  • 32.
    move original methodto super class does not deal with bodies ∙ “Move Original Method to Super Class” law [Borba et al., 2004] expresses transformational semantic equivalence. ∙ In our case, no method declarations are being moved but rather bodies. 10
  • 33.
  • 34.
    target methods withmultiple source methods interface Collection<E> { boolean isEmpty();} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} 12
  • 35.
    target methods withmultiple source methods interface Collection<E> { boolean isEmpty();} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} ∙ May not have a one-to-one correspondence between source and target methods. 12
  • 36.
    target methods withmultiple source methods interface Collection<E> { boolean isEmpty();} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} ∙ May not have a one-to-one correspondence between source and target methods. ∙ Migrating any of the source methods passing preconditions would be safe. 12
  • 37.
    target methods withmultiple source methods interface Collection<E> { boolean isEmpty();} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} ∙ May not have a one-to-one correspondence between source and target methods. ∙ Migrating any of the source methods passing preconditions would be safe. ∙ Choose the largest number of “equivalent” source methods. 12
  • 38.
    target methods withmultiple source methods interface Collection<E> { default boolean isEmpty() {return this.size() == 0;}} abstract class AbsList<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsStack<E> implements Collection<E> { @Override public boolean isEmpty() { return this.size() == 0;}} abstract class AbsSet<E> implements Collection<E> { @Override public boolean isEmpty() { int size = this.size(); return size == 0;}} ∙ May not have a one-to-one correspondence between source and target methods. ∙ Migrating any of the source methods passing preconditions would be safe. ∙ Choose the largest number of “equivalent” source methods. 12
  • 39.
    interfaces cannot declareinstance fields interface Collection<E> { int size();} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? 13
  • 40.
    interfaces cannot declareinstance fields interface Collection<E> { default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? 13
  • 41.
    interfaces cannot declareinstance fields interface Collection<E> { default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? 13
  • 42.
    interfaces cannot declareinstance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? 13
  • 43.
    interfaces cannot declareinstance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? ∙ Interfaces cannot declare instance fields. 13
  • 44.
    interfaces cannot declareinstance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? ∙ Interfaces cannot declare instance fields. 13
  • 45.
    interfaces cannot declareinstance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? ∙ Interfaces cannot declare instance fields. Question In general, how can we guarantee that migration results in a type-correct transformation? 13
  • 46.
    interfaces cannot declareinstance fields interface Collection<E> { Object[] elems; int size; default int size() {return this.size;}} abstract class AbsList<E> implements Collection<E> { Object[] elems; int size; @Override public int size() {return this.size;}} ∙ Migrate AbsList.size() to Collection as a default method? ∙ size() accesses instance fields; migrate them to Collection? ∙ Interfaces cannot declare instance fields. Question In general, how can we guarantee that migration results in a type-correct transformation? Answer Use type constraints [Palsberg and Schwartzbach, 1994; Tip et al., 2011] to check refactoring preconditions. 13
  • 47.
    using type constraintsas refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. 14
  • 48.
    using type constraintsas refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. ∙ A complete program is type-correct if all constraints implied by all program elements hold. 14
  • 49.
    using type constraintsas refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. ∙ A complete program is type-correct if all constraints implied by all program elements hold. program construct implied type constraint(s) access E.f to field F [E.f] ≜ [F] (1) [E] ≤ Decl(F) (2) 14
  • 50.
    using type constraintsas refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. ∙ A complete program is type-correct if all constraints implied by all program elements hold. program construct implied type constraint(s) access E.f to field F [E.f] ≜ [F] (1) [E] ≤ Decl(F) (2) Migrating size() to Collection would imply [this] = Collection. interface Collection<E> { default int size() {return this.size;}} 14
  • 51.
    using type constraintsas refactoring preconditions ∙ Type constraints denote the subtyping relationships for each program element that must hold between corresponding expressions for that portion to be considered well-typed. ∙ A complete program is type-correct if all constraints implied by all program elements hold. program construct implied type constraint(s) access E.f to field F [E.f] ≜ [F] (1) [E] ≤ Decl(F) (2) Migrating size() to Collection would imply [this] = Collection. interface Collection<E> { default int size() {return this.size;}} This violates constraint (2) that [this] ≤ [AbsList]. abstract class AbsList<E> implements Collection<E> { @Override public int size() {return this.size;}} 14
  • 52.
    new type constraints,definitions, and semantics preservation ∙ Extend [Tip et al., 2011] with new constraints, new definitions, and semantics preservation for default methods. ∙ See paper for more details. 15
  • 53.
    preserving semantics inlight of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { void removeLast(); void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsQueue. 16
  • 54.
    preserving semantics inlight of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { void removeLast(); void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsQueue. ∙ Can we migrate removeLast() from AbsQueue to Queue? 16
  • 55.
    preserving semantics inlight of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsQueue. ∙ Can we migrate removeLast() from AbsQueue to Queue? 16
  • 56.
    preserving semantics inlight of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to Queue? ∙ Can we migrate removeLast() from AbsQueue to Queue? 16
  • 57.
    preserving semantics inlight of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsList. ∙ Can we migrate removeLast() from AbsQueue to Queue? ∙ Now dispatches to AbsList as classes take precedence! 16
  • 58.
    preserving semantics inlight of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsList. ∙ Can we migrate removeLast() from AbsQueue to Queue? ∙ Now dispatches to AbsList as classes take precedence! ∙ Queue loses “tie” with AbsList. 16
  • 59.
    preserving semantics inlight of multiple inheritance abstract class AbsList<E> implements Collection<E> { @Override public void removeLast() { throw new UnsupportedOperationException();}} interface Queue<E> extends Collection<E> { default void removeLast(); { if (!isEmpty()) this.setSize(this.size() - 1);}} void setSize(int i);} abstract class AbsQueue<E> extends AbsList<E> implements Queue<E> { @Override public void removeLast() { if (!isEmpty()) this.setSize(this.size() - 1);}} new AbsQueue<Integer>() {}.removeLast(); // to AbsQueue. ∙ Can we migrate removeLast() from AbsQueue to Queue? ∙ Now dispatches to AbsList as classes take precedence! ∙ Queue loses “tie” with AbsList. ∙ Disallow methods that override in both classes and interfaces. 16
  • 60.
    eclipse plug-in andcase study subject KL KM cnds dflts fps δ -δ tm (s) ArtOfIllusion 118 6.94 16 1 34 1 0 3.65 Azureus 599 3.98 747 116 1366 31 2 61.83 Colt 36 3.77 69 4 140 3 0 6.76 elasticsearch 585 47.87 339 69 644 21 4 83.30 Java8 291 30.99 299 93 775 25 10 64.66 JavaPush 6 0.77 1 0 4 0 0 1.02 JGraph 13 1.47 16 2 21 1 0 3.12 JHotDraw 32 3.60 181 46 282 8 0 7.75 JUnit 26 3.58 9 0 25 0 0 0.79 MWDumper 5 0.40 11 0 24 0 0 0.29 osgi 18 1.81 13 3 11 2 0 0.76 rdp4j 2 0.26 10 8 2 1 0 1.10 spring 506 53.51 776 150 1459 50 13 91.68 Tomcat 176 16.15 233 31 399 13 0 13.81 verbose 4 0.55 1 0 1 0 0 0.55 VietPad 11 0.58 15 0 26 0 0 0.36 Violet 27 2.06 104 40 102 5 1 3.54 Wezzle2D 35 2.18 87 13 181 5 0 4.26 ZKoss 185 15.95 394 76 684 0 0 33.95 Totals: 2677 232.2 3321 652 6180 166 30 383.17 ∙ Implemented as an open source Eclipse plug-in. ∙ Evaluated on 19 Java programs of varying size and domain. ∙ Automatically migrated 19.63% (column dflts) of candidate despite conservatism. ∙ Running time (column tm (s)) averaged ∼0.144 secs/KLOC. 17
  • 61.
    refactoring precondition failuredistribution ∙ Field and method inaccessibility from the destination interface accounted for largest number of errors. ∙ Next largest failure due to instance field accesses (failures of constraint (2)). 18
  • 62.
    preliminary pull requeststudy ∙ Submitted 19 pull requests to Java projects on GitHub. ∙ 4 were successfully merged, 5 are still open, and 10 were closed without merging. ∙ Merged projects totaled 163 watches, 1071 stars, and 180 forks. ∙ Projects rejecting requests citing reasons such as: ∙ They had not yet moved or were in the process of moving to Java 8. ∙ Needed to support older Java clients (Android). 19
  • 63.
    summary ∙ Efficient, fully-automated,semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. 20
  • 64.
    summary ∙ Efficient, fully-automated,semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. 20
  • 65.
    summary ∙ Efficient, fully-automated,semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. ∙ Tool scales and refactored 19.63% of methods possibly participating in the pattern. 20
  • 66.
    summary ∙ Efficient, fully-automated,semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. ∙ Tool scales and refactored 19.63% of methods possibly participating in the pattern. ∙ 4 pull requests merged into GitHub repositories, including large, widely used frameworks from reputable organizations. 20
  • 67.
    summary ∙ Efficient, fully-automated,semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. ∙ Tool scales and refactored 19.63% of methods possibly participating in the pattern. ∙ 4 pull requests merged into GitHub repositories, including large, widely used frameworks from reputable organizations. ∙ Studies highlight pattern usage and gives possible insight to language designers on construct applicability to existing software. 20
  • 68.
    summary ∙ Efficient, fully-automated,semantics-preserving refactoring approach based on type constraints that migrates the skeletal implementation pattern in legacy Java code to instead use default methods. ∙ Implemented as an Eclipse IDE plug-in (available at http://cuny.is/interefact) and evaluated on 19 open source projects. ∙ Tool scales and refactored 19.63% of methods possibly participating in the pattern. ∙ 4 pull requests merged into GitHub repositories, including large, widely used frameworks from reputable organizations. ∙ Studies highlight pattern usage and gives possible insight to language designers on construct applicability to existing software. ∙ Graduate positions available! http://bit.ly/cunygrad 20
  • 69.
    precondition failures breakdown #Precondition Fails P1 MethodContainsInconsistentParameterAnnotations 1 P2 MethodContainsCallToProtectedObjectMethod 1 P3 TypeVariableNotAvailable 10 P4 DestinationInterfaceIsFunctional 17 P5 TargetMethodHasMultipleSourceMethods 19 P6 MethodContainsIncompatibleParameterTypeParameters 42 P7 NoMethodsWithMultipleCandidateDestinations 53 P8 TypeNotAccessible 64 P9 SourceMethodImplementsMultipleMethods 72 P10 SourceMethodProvidesImplementationsForMultipleMethods 79 P11 MethodContainsTypeIncompatibleThisReference 79 P12 IncompatibleMethodReturnTypes 104 P13 ExceptionTypeMismatch 105 P14 MethodContainsSuperReference 147 P15 SourceMethodOverridesClassMethod 258 P16 AnnotationMismatch 305 P17 SourceMethodAccessesInstanceField 463 P18 MethodNotAccessible 1,679 P19 FieldNotAccessible 2,565 Table: Precondition failures. 21
  • 70.
    for further reading JoshuaBloch. Effective Java. Prentice Hall, 2008. Paulo Borba, Augusto Sampaio, Ana Cavalcanti, and Márcio Cornélio. Algebraic reasoning for object-oriented programming. Science of Computer Programming, 52 (1-3):53–100, August 2004. ISSN 0167-6423. doi: 10.1016/j.scico.2004.03.003. Martin Fowler. Refactoring: Improving the Design of Existing Code. Addison-Wesley Professional, 1999. Brian Goetz. Interface evolution via virtual extensions methods. Technical report, Oracle Corporation, June 2011. URL http://cr.openjdk.java.net/ ~briangoetz/lambda/Defender%20Methods%20v4.pdf. Cay S. Horstmann. Java SE 8 for the Really Impatient. Addison-Wesley Professional, 2014. Raffi Khatchadourian, Olivia Moore, and Hidehiko Masuhara. Towards improving interface modularity in legacy java software through automated refactoring. In Companion Proceedings of the 15th International Conference on Modularity, MODULARITY Companion 2016, pages 104–106, New York, NY, USA, 2016. ACM. ISBN 978-1-4503-4033-5. doi: 10.1145/2892664.2892681. Jens Palsberg and Michael I. Schwartzbach. Object-oriented type systems. John Wiley and Sons Ltd., 1994. ISBN 0-471-94128-X. Frank Tip, Robert M. Fuhrer, Adam Kieżun, Michael D. Ernst, Ittai Balaban, and Bjorn De Sutter. Refactoring using type constraints. ACM Transactions on Programming Languages and Systems, 33(3):9:1–9:47, May 2011. ISSN 0164-0925. doi: 10.1145/1961204.1961205. 22