A Framework for Evolutions in FeatureMapper
4.3 Possibilities for Extension
4.3.1 Adding New Evolutions
When using an evolution in Java source code, the generic refactoring mechanism of Refactory is merely used to execute the evolution on a suitable selection after triggering it from the context menu. For this purpose, it suffices to create a minimalistic abstract refactoring. First, a role model with merely one role is created. Then, a refactoring specification loading the objects for this role has to be composed. Even though the objects in the refactoring specification are not used themselves, loading them is required for Refactory to located objects for the respective roles which can then be used by the post processor. Otherwise, an evolution would be passed an empty input list. In Listing 4.7 and Listing 4.8 both created artifacts are shown.
Listing 4.7: Example of a minimalistic role model.
RoleModel SimpleEvolution { input super ROLE Element;
}
The created role model and refactoring specification then have to be made available for use by Refactory by binding the extension points org.emftext.refactoring.rolemodel and org.emftext.refactoring.refspec respectively. This concludes creating the generic part of an evolution. The following steps depend on the concrete model that should be evolved. The
Listing 4.8: Example of a minimalistic refactoring specification.
REFACTORING FOR <SimpleEvolution>
STEPS {
/∗∗ Dummy ∗/
object element := Element from filter(INPUT);
}
explanations will use the implementation of the Duplicate Feature evolution as example of how to create a problem space evolution. An analog procedure can be used for solution space evolutions.
The last paragraphs of this section will explain how to modify the procedure in order to create a solution space evolution instead.
As the feature tree is the only type of model in the problem space, genericity is not required for problem space evolutions. Instead, the specific knowledge of the characteristics of a the feature tree can be used to create sophisticated evolutions. First, the generic basis consisting of role model and refactoring specification has to be bound to an element of a metamodel. In the case of Duplicate Feature, that is the class Feature of the feature model. For this purpose, a file called “DuplicateFeature.rolemapping” is created with the content presented in Listing 4.9.
Listing 4.9: Example of a role mapping for Duplicate Feature.
ROLEMODELMAPPING FOR <http://www.tudresden.de/feature>
DuplicateFeature maps <SimpleEvolution> { Element := Feature;
}
After creating the file, it has to be registered using the extension point org.emftext.
refactoring.rolemapping. Finally, the real evolution has to be implemented. In Section 4.2.1, it was explained that a design decision has been made to cleanly separate the implementation of the evolution system of this thesis from Refactory. However, both systems cooperate in order to perform modifications to models and maintain the mapping of a product line. In consequence, the steps of an evolution are implemented in one class and the connection between Refactory and the evolution is established using another class.
Evolutions are implemented as subclasses of the abstract class AbstractEvolution. For the case of problem space evolutions, a skeleton implementation exists in the class Abstract-ProblemSpaceEvolution. These classes are self sufficient and can be executed without being integrated into the control flow of Refactory. Connecting the evolution with Refactory is done by deriving a class from AbstractEvolutionAdapter. The sole purpose of the adapter is to translate the objects playing particular roles in Refactory to suitable input parameters to create and configure an evolution. The principle structure of an EvolutionAdapter for the Dupli-cate Feature evolution is shown in Listing 4.10. The details on how to acquire values for the parameters of an evolution will be explained later.
Listing 4.10: Principle structure of the implementation of the evolution adapter for Duplicate Feature.
public class DuplicateFeatureEvolutionAdapter extends
AbstractProblemSpaceEvolutionAdapter<DuplicateFeatureEvolution> {
@Override
protected DuplicateFeatureEvolution createAndConfigureEvolution() throws AcquireParametersException {
Feature featureToDuplicate = ...;
String duplicateFeatureName = ...;
boolean duplicateChildren = ...;
return new DuplicateFeatureEvolution(getRefactoredModel(),
getResourceSet(), getIsFakeRun(), getUriMap(), featureToDuplicate, duplicateFeatureName, duplicateChildren);
} }
An evolution adapter then has to be registered as post processor to the created concrete evo-lution in the role mapping via the extension point org.emftext.refactoring.postprocessor.
To identify a particular evolution, its name and the URI of the target meta model have to be provided along with the post processor. Once the adapter is registered as post processor, it is executed when the appropriate menu entry for the evolution was selected. After that, the real evolution in source code is instantiated and executed.
A problem space evolution provides methods for the principle procedure of performing an evolution on the feature tree in the form of a template method [GHJV95]. The mandatory part, which has to be implemented, is the method evolve() that is intended to perform the actual modification of the input model. Before the evolve() method is called, preconditions that have to be met for the evolution to be applicable can optionally be checked. For this purpose, the method checkPreconditions() can be overwritten. These two methods suffice to implement intraspatial evolutions. However, interspatial evolutions require a subsequent remapping to maintain the consistency of the product line. For this purpose, an adequate remapping plan for the evolution has to be created. The class AbstractEvolution presents the method createRemappingPlan() that can be overwritten to add appropriate remapping steps to the remapping plan. The created plan is then executed after the modifications of the evolution as part of the control flow specified by the template method of the evolution. The principle structure of the Duplicate Feature evolution is shown in Listing 4.11.
With this setup, the evolution is operational. No further registrations via extension points are required, as the evolution adapter creates the evolution appropriate for the executed role mapping. However, at the current stage of development, the evolution adapter is not yet able to acquire the parameters required to configure the evolution as this part has been omitted in the explanations. There are two means for retrieving parameters for an evolution that both are
Listing 4.11: Principle structure of the implementation of the Duplicate Feature evolution.
public class DuplicateFeatureEvolution extends AbstractDuplicateEvolution {
public DuplicateFeatureEvolution(EObject refactoredModel,
ResourceSet resourceSet, boolean isPreviewMode, Map<EObject, URI> uriMap, Feature featureToDuplicate, String duplicateFeatureName,
boolean duplicateChildren) {
super(refactoredModel, resourceSet, isPreviewMode, uriMap, featureToDuplicate);
//...
}
@Override
protected void checkPreconditions() throws PreconditionViolatedException { //...
}
@Override
protected void evolve() throws EvolutionException { //...
}
@Override
protected RemappingPlan createRemappingPlan() throws RemappingSpecificationTranslationException {
RemappingPlan plan = super.createRemappingPlan();
//...
return plan;
} }
used as part of the Duplicate Feature evolution. First, objects playing a particular role of the role model can be retrieved from Refactory. The evolution adapter has dedicated methods for this purpose. For example, getMandatoryFirstObjectForRole() and getMandatoryObjects-ForRole() can retrieve one object or a list of objects playing a role identified by name. For the Duplicate Feature evolution, the feature that is to be duplicated is retrieved in this fashion.
As second option, it is possible to offer a configuration page for an evolution as part of the evolution wizard and then retrieve configuration parameters from it. A wizard page used for configuration should subclass AbstractParameterPage, which provides the basic structure of the user interface. The derived class then has to be registered with Refactory using the extension point org.emftext.refactoring.customwizardpage, which requires equivalent input as when registering a post processor such as the evolution adapter. In Listing 4.12, the outline of the
parameter page for Duplicate Feature is displayed as an example.
Listing 4.12: Principle structure of the parameter page for Duplicate Feature.
public class DuplicateFeatureParameterPage extends AbstractParameterPage {
@Override
protected Control doCreateControl(Composite parent) {
Composite composite = new Composite(parent, SWT.NULL);
//...
return composite;
} }
This parameter page is now displayed whenever the Duplicate Feature evolution is executed.
The evolution adapter holds a list of the parameter pages registered for a particular evolution.
Furthermore, it provides an auxiliary method called getMandatoryWizardPage() to retrieve a specific parameter page, which takes the class of the wizard page to fetch as parameter. Through this, it is now possible to complete the implementation of the principle version of the evolution adapter presented in Listing 4.10. The complete implementation of the evolution adapter for the Duplicate Feature evolution can be seen in Listing 4.13.
The parameter pages of the evolution wizard are created using the standard widget toolkit (SWT)4. It is worth noting that the evolution adapter accesses the values of these pages after the user interface components have been disposed by SWT. This means that a direct access to user interface components (such as text fields) is no longer possible at this point in the control flow. As a remedy, the values of parameters should be made attributes of the parameter page that are set whenever the value of their respective user interface control is modified. Through this, the current value of a parameter can be retrieved from a parameter page even after the user interface components have been disposed.
Even though this example presented the process of creating a problem space evolution, im-plementing a solution space evolution can easily be derived from the explanations. The essential difference is to not use AbstractProblemSpaceEvolution and AbstractProblemSpaceEvolu-tionAdapter as base classes but the respective counterparts for the solution space. Furthermore, feature remapping steps of the remapping plan are abandoned in favor of the respective object remapping steps. The rest of the procedure is analog for the solution space.
With the aid of this example, it is now possible to add new evolutions to the system. How-ever, sometimes it might also be necessary to adapt existing declaratively specified interspatial evolutions of Refactory to perform a remapping in order to keep a product line consistent. This procedure is explained in the following section.
4http://www.eclipse.org/swt/
Listing 4.13: Complete implementation of the evolution adapter for Duplicate Feature.
public class DuplicateFeatureEvolutionAdapter extends AbstractEvolutionAdapter<DuplicateFeatureEvolution> {
@Override
protected DuplicateFeatureEvolution createAndConfigureEvolution() throws AcquireParametersException {
Feature featureToDuplicate = getMandatoryFirstObjectForRole("Element", Feature.class);
DuplicateFeatureParameterPage parameterPage = getMandatoryWizardPage(
DuplicateFeatureParameterPage.class);
String duplicateFeatureName = parameterPage.getElementName();
boolean duplicateChildren = parameterPage.getDuplicateChildren();
return new DuplicateFeatureEvolution(getRefactoredModel(),
getResourceSet(), getIsFakeRun(), getUriMap(), featureToDuplicate, duplicateFeatureName, duplicateChildren);
} }