Idea

Whenever a software component is changed due to bug fixing or the addition of new features, client applications might break. ComeBack! uses the information about the changes that were applied to generate an adapter layer for retaining backward compatibility on a binary level. At the moment we only support .NET but we are already working on an extension to Java.

ComeBack! consists of language-dependent frontends for extracting information about object-oriented structures from the binaries and backends for serializing such information to binary format back again. Inbetween, the information is transformed using Prolog rules describing the compensations suitable for each applied change.

Example: Extract Subclass

The following example is to outline the basic modus operandi of the ComeBack! tool. In the beginning there was a single class Customer. Then it was refactored using the ExtractSubclass refactoring in order to be able to better distinguish between ordinary and VIP customers. This entailed not only the creation of a new class VIPCustomer but also the movement of the method GetDiscount().

The Extract Subclass refactoring breaks the plugin

After that refactoring client code will break and our adaptation tool is applied. The first thing it does is extracting the current API from the .NET assembly into a Prolog fact base shown below.

classReferences('Customer', []).
classReferences('VIPCustomer', []).

instanceVariablesDefinedBy('Customer', []).
instanceVariablesDefinedBy('VIPCustomer', []).

isClass('Customer').
isClass('VIPCustomer').

method('Customer', 'GetName', 'Customer:GetName()string', []).
method('VIPCustomer', 'GetDiscount', 'VIPCustomer:GetDiscount()double', []).

senders('Customer', 'GetName', [], []).
senders('VIPCustomer', 'GetDiscount', [], []).

superclass('Customer', 'VIPCustomer').
superclass('System.Object', 'Customer').

Using the comeback for the ExtractSubclass refactoring the above fact base is then transformed, so that it reflects the structure of the adapter layer, that will be inserted between the client code and the refactored class. The transformation is not applied to the original classes, instead adapter classes are generated beforehand.

isClass('VIPCustomeradapter').
isClass('Customeradapter').
isClass('Customer').
isClass('VIPCustomer').

classReferences('VIPCustomeradapter', []).
classReferences('Customeradapter', []).
classReferences('Customer', []).
classReferences('VIPCustomer', []).

superclass('Customeradapter', 'VIPCustomeradapter').
superclass('System.Object', 'Customeradapter').
superclass('Customer', 'VIPCustomer').
superclass('System.Object', 'Customer').

method('Customer', 'GetName', 'Customer:GetName()string', []).
method('VIPCustomer', 'GetDiscount', 'VIPCustomer:GetDiscount()double', []).

referencesToInstanceVariable('VIPCustomer', 'VIPCustomer', []).
referencesToInstanceVariable('Customer', 'Customer', []).

senders('Customer', 'GetName', [], []).
senders('VIPCustomer', 'GetDiscount', [], []).

classOf('VIPCustomer', 'VIPCustomer', ['VIPCustomeradapter']).
classOf('Customer', 'Customer', ['Customeradapter']).

instanceVariablesDefinedBy('VIPCustomer', ['VIPCustomer']).
instanceVariablesDefinedBy('VIPCustomeradapter', []).
instanceVariablesDefinedBy('Customer', ['Customer']).
instanceVariablesDefinedBy('Customeradapter', []).

isAdapter('VIPCustomer', 'VIPCustomeradapter').
isAdapter('Customer', 'Customeradapter').

Finally, the adapter layer is created by constructing a .NET assembly that will be loaded together with the client code. The latter will not break anymore, because the comeback rule applied generated a view on the previous version of the class Customer.

Generated adapter retains binary backward compatibility