• Ei tuloksia

Framework Specialization Assistance with JavaFrames

Chapter 6 Applying Task Generation to Framework Specialization

6.1 The JavaFrames Environment

6.1.3 Framework Specialization Assistance with JavaFrames

In this chapter we illustrate the framework specialization support provided by JavaFrames. First we describe the essential parts of the user interface of Java-Frames, and then, as an example, we look at how JavaFrames guides the use of the Abstract Factory pattern discussed in Chapter 6.1.2.

JavaFrames User Interface

JavaFrames is implemented as a perspective in the freely available Eclipse Java IDE [www.eclipse.org]. The main parts of JavaFrames’ user interface are shown in Figure 6.6. The user interface consists of a number of special views that can be mixed with the standard views of Eclipse.

The Architecture view shows all the available patterns that constitute the system architecture. The Java editor is used for representing and editing the Java source code of the application currently under development.

Figure 6.7 illustrates the structure and the relationships of the patterns and tasks displayed in the remaining JavaFrames views in Figure 6.6. There are two pat-terns involved: Figure and MethodOverriding. The MethodOverriding pattern has two class roles: Base and Sub. Both of these roles have a child role called method. Pattern Figure is a context-specific specialization of MethodOverriding.

Initially, the user has created an empty pattern (Figure) and specified a generali-zation relationship between Figure and MethodOverriding. After that, the task

automaton has generated task Provide class ‘Base’ from the Base role of the MethodOverriding pattern. The task generation has been done as explained in Algorithm 5.1and Algorithm 5.2 (recall Chapter 5.2).

Figure 6.6: The main parts of the user interface of JavaFrames

In the situation captured in Figure 6.6 and Figure 6.7, the user has so far executed the first task generated from role Base by associating the role with Java class Figure. The bound specialization of role Base, i.e. role Figure, represents the binding. After the task execution, the task automaton has generated three new tasks: (a) Provide a Java method that might be overridden, (b) Provide class

‘Base’ (2), and (c) Provide a subclass for ‘Figure’. Again, task generation has been done according to the algorithms in Chapter 5.2.

The global task list (see Figure 6.6) shows every undone task currently available for a selected pattern. In Figure 6.6, the user has chosen to examine the contents of the Figure pattern in the global task list. As the result, the previously men-tioned three tasks are shown: (a) Provide a Java method that might be overrid-den, (b) Provide class ‘Base’ (2), and (c) Provide a subclass for ‘Figure’. Note that the global task list is flat, i.e. one-dimensional, so that you can see all the undone tasks of the pattern at once. The order of the tasks can be changed ac-cording to different sorting criteria; by default the tasks generated last are at the

Local task list

System architecture: patterns Java editor

Role dependencies Global task list

Role scripts

Role specializations

Roles and

constraints Task description

Role generalizations

top of the list. The mandatory and optional tasks are marked with dark grey and white dots, respectively. (The dark grey dots are actually red in the user interface.

All the tasks are optional in the example.) Tasks are executed by double-clicking the task title. This opens a dialog for selecting a dedicated wizard that provides help in the actual task execution. Wizards will be discussed in more detail later.

Figure 6.7: A task example

The left-hand side of the Pattern view in Figure 6.6 shows the roles and con-straints of a selected pattern. The user has selected pattern Figure. The roles are represented as a role tree structured according to the child–parent-relationships between the roles. There is only one role in pattern Figure: the bound specializa-tion of role Base, i.e. the Figure role. The constraints appear as leaves under the roles. (There are no constraints in role Figure.) By double-clicking a bound role, you can view the Java program element or other target domain entity playing the role. The user has clicked the Figure role and, consequently, the source code for the Figure class has been opened in the Java editor.

Whenever a role is selected from the role tree on the left in Pattern View, the lo-cal task list on the right shows the related tasks. A more detailed task description is available for a chosen task. The role tree points out the locations of the cur-rently available tasks with special symbols attached to the branch nodes: an ar-row in a branch node denotes that there are tasks somewhere in the subtree, whereas a dark grey or a white dot indicates mandatory or optional tasks being associated, respectively, with the branch itself. The title line of the Pattern view reports the total number of mandatory and optional tasks currently available in the pattern.

For a task selected from the role tree, the local task list shows the tasks generated from the child roles of the selected role’s generalization. In Figure 6.6, the user has selected the root level of pattern Figure. Therefore, the tasks generated from those roles in MethodOverriding that do not have child roles (i.e. Base and Sub) are shown to the user. These tasks are: (a) Provide class ‘Base’ (2) and (b) Pro-vide a subclass for ‘Figure’. Note that the local task list is just an alternative, more structured, view to the same tasks that are represented in the global task list.

Base Sub (a parent role)

MethodOverriding

method method (a child role)

Figure

Provide a subclass for ‘Figure’

Provide class ‘Base’

Provide a Java method that might be overridden Figure

(a parent role)

(a child role) *

* *

Provide class

‘Base’ (2)

Besides Tasks, the right-hand side of the Pattern view has two other tabs: Prop-erties and Hierarchy. The PropProp-erties tab shows the dependencies and scripts of the selected role. Those dependencies and scripts that are inherited from a gener-alization role are drawn with a light-grey color in order to separate them from the properties introduced by the role itself. In Figure 6.6, the user has selected to view the dependencies and scripts of the /Sub/method role in pattern Method-Overriding.

The Hierarchy tab shows the generalization and specialization roles of the se-lected role. By double-clicking any of the roles, you can view the corresponding pattern in the Architecture and Pattern views. The focus of the Pattern view is automatically changed to highlight the selected role. You can navigate similarly from a task to the role from which the task was generated. The Hierarchy tab al-lows the user to navigate conveniently through pattern hierarchies. In Figure 6.6, the user has selected to view the generalizations and specializations of role Base in pattern MethodOverriding.

A Pattern in a Framework Reuse Interface Specification

In the following chapters we illustrate JavaFrames’ framework specialization as-sistance capabilities with an example based on the Abstract Factory pattern. (Re-call Chapter 6.1.2.) Here we assume that we have analyzed the source code of a framework (arrow 4 in Figure 6.1 on page 91), and, based on the analysis (arrow 5), we have decided to apply the SimpleAbstractFactory pattern in developing a part of the reuse interface specification for the framework (arrows 2 and 6).

Figure 6.8 depicts the original SimpleAbstractFactory pattern and its framework-specific adaptation, UIFactory. The latter is part of the framework reuse interface specification in this example.

The upper graph inside the UIFactory pattern represents the roles and dependen-cies of the pattern. The lower graph represents the tasks that were (a) generated based on the SimpleAbstractFactory pattern and (b) executed to create the roles of the UIFactory pattern. For the sake of clarity, the relationships between a task and the associated generalization and specialization roles are visualized only for the first executed task: Provide an abstract factory. That task was generated from role AbstractFactory and executed to create role UIFactory. Note, however, that all the tasks are numbered according to the order of their execution, and the number of a task is also attached to corresponding generalization and specializa-tion roles. Thus, we can see, for example, that the second executed task has been:

Provide an abstract product. That task was generated from the AbstractProduct generalization role and executed to create the Button specialization role. Note that the tasks that contain dependencies to other tasks have not been generated until the dependency target tasks have been executed. For instance, task 5 was not generated until tasks 1 and 2 were executed. (Task generation was explained in detail in Chapter 5.2.)

On the whole, pattern UIFactory contains ten roles. The roles on the left — But-ton, createButBut-ton, UIFactory, createWindow, and Window — are already bound

to program elements in the framework source code. The rest of the roles — UserButton, createButton, UserUIFactory, createWindow, and UserWindow — are not yet bound. Thus, new tasks would be generated from them, if we started using the UIFactory pattern. The unbound roles represent the application-side entities that are to be created or located by the framework specializer. The un-bound roles have been made by postponing tasks, whereas the un-bound roles have been created by executing tasks via locating suitable framework source code enti-ties for playing the roles. Note that pattern UIFactory inherits all the constraints, scripts, and dependencies specified in pattern SimpleAbstractFactory. (Recall the discussion about role inheritance, bound and unbound roles, as well as task post-ponement in Chapter 5.3.)

Figure 6.8: The Abstract Factory pattern and its framework-specific adaptation, UI-Factory

We shall discuss the creation of framework reuse interface specifications in more detail in Chapter 6.2. However, in the next few chapters we show how

frame-«pattern»

UIFactory

Provide an abstract factory…

Provide an abstract product… (2) Provide an abstract

work reuse interface specifications are used in framework specialization with JavaFrames.

Guidance for Proceeding in Framework Specialization

On a general level, an application developer adapts a framework with Java-Frames by (1) browsing the patterns available in the reuse interface specification of the framework or in the library of general patterns and (2) selecting and apply-ing the patterns that provide support for addapply-ing the desired features into the ap-plication under development. These actions correspond to arrows 7 and 8 in Figure 6.1 (page 91), respectively.

The left side of Figure 6.9 shows a snapshot of the first steps in specializing our example framework. The application developer has previously created a new ar-chitecture called Zoo Calendar Application for the application-specific patterns that will be extended from framework-specific patterns during framework spe-cialization. At the moment, the developer is browsing the patterns available in the framework reuse interface specification. Those patterns are represented in architecture User Interface Framework. The patterns are provided with free-form documentation that helps in choosing a suitable pattern. In the figure, the user has selected pattern UIFactory and the description of the pattern is shown below.

Figure 6.9: Browsing the patterns (left) and the first specialization task (right)

The view on the right in Figure 6.9 shows how the application developer has started applying pattern UIFactory; the developer has created a new pattern (ZooCalendar) in architecture Zoo Calendar Application and added an extension relationship from the ZooCalendar pattern to the UIFactory pattern. The global task list shows all the tasks currently available; at the moment there is one task generated from role UserUIFactory in the UIFactory pattern (recall Figure 6.8):

Subclass ‘UIFactory’ to provide a concrete factory. The task is mandatory as the dark grey dot indicates. The left-side of the Pattern view shows the roles of the pattern: initially there are roles automatically cloned from the bound roles of the generalization pattern UIFactory: Button, UIFactory, createButton, createWin-dow, and Window. The icons of these roles are represented behind grey shades to separate them from roles created after executing tasks in the current pattern. In practice, the new roles that will be created are related to application-side entities and the roles behind clouds are related to framework-side entities.

In the Tasks tab of the right-hand side of Figure 6.9, the application developer has selected the task available at the root level of the role tree. The more detailed task description attached to the task is represented below. The description is pro-duced based on the taskDescription script of role UIFactory.

Generating Code

Figure 6.10 shows how the first task is executed by generating code for a Java class. The Java class will be playing role UserUIFactory.

The top left corner in the figure shows that the user has opened a context-sensitive menu for choosing an appropriate action. The actions Postpone Task, Bind to Role, and Show Task Originator Role are especially useful when we are creating new patterns that are supposed to be used in other patterns. Task post-ponement and binding tasks to existing roles were illustrated in Figure 5.7 (page 87). Viewing the task originator role is a useful feature when debugging patterns.

Actions Show in Pattern View and Show in Global Task View can be used to view the task in different contexts: either together with the other tasks generated from the parent role of the generalization of the selected task or with all the other tasks of the pattern. In Figure 6.10, however, the developer chooses the action Perform Task. As a result, the Perform Task dialog is shown to the developer.

That dialog shows the wizards applicable for performing the task. In this case there are two wizards available: Generate Java type and Locate Java type. The types of wizards available depend on the type of the task to be executed. Typi-cally there is a wizard both for generating new code and for locating an existing target domain entity for playing the role from which the task was generated. Note that it is possible to add new types of wizards for executing tasks by extending the JavaFrames framework.

In Figure 6.10, the developer chooses to generate new code. Consequently, the code generation wizard shows a default implementation generated based on the defaultTemplate script (the lower right corner of the figure). By default, Java-Frames suggests name ConcreteUIFactory for a subclass of UIFactory. This has been specified in the role from which the task was generated. However, the ap-plication developer chooses to use name LinuxUIFactory instead. The last step in Figure 6.10 shows the generated Java class and the created role LinuxUIFactory that is bound to the class.

In principle, it would be possible to insert the generated default code straight to the Java project. In JavaFrames, however, we have chosen to let the developer to modify the code first. This is mainly because the bindings from pattern roles to Java elements are implemented using the names of the Java elements. (This pol-icy was discussed in Chapter 4.3.2.) Typically the application developer wishes to change the name of the program element generated by default. Thus, it is more convenient for the developer to be able to specify the name of the program ele-ment before the binding is made so that the developer does not have to fix the role binding right after code generation.

Besides the new role and the new class bound to the new role, the last step in Figure 6.10 also shows the new tasks available after the code generation: there are now tasks for creating concrete subclasses for the interfaces represented in the framework: Window and Button. Also, it is possible to create a new subclass for UIFactory.

Figure 6.10: Executing a task by generating new code

Instead of executing tasks one-by-one it is sometimes more practical to perform all the tasks related to a specific target domain entity. Figure 6.11 shows an ex-ample of performing a group of tasks at once. The application developer has first selected role LinuxUIFactory (that is bound to the corresponding class) and then chosen to perform all the tasks available in the local task list for that role. The tasks suggest the application developer to override the abstract methods declared in UIFactory. As a result of performing the tasks, proper method implementa-tions for createButton and createWindow are generated. Note that the generated methods return instances of the proper concrete classes LinuxButton and Linux-Window. The default implementation for the createWindow method, for example, was generated based on the defaultTemplate script that role createWindow (in pattern UIFactory) inherited from role concreteCreate (in pattern SimpleAb-stractFactory). See Figure 6.8 to recall the relationship between roles

createWin-dow and concreteCreate. Lines 32 – 36 in Code example 6.2 (page 92) show the inherited defaultTemplate script.

Figure 6.11: Performing a group of tasks at once

Detecting and Repairing Constraint Violations

After generating a program element for playing a certain role in a pattern, the ap-plication developer can change the element freely. This means that even though the generated default implementation for the program element followed the con-straints specified in the role, after possible user-made modifications constraint violations might emerge. To detect these violations, JavaFrames constantly moni-tors changes in the application source code currently under development.

Figure 6.12 shows a snapshot of a later stage of applying the SimpleAbstractFac-tory pattern. The application developer has created a new concrete facSimpleAbstractFac-tory, Ma-cOSUIFactory, for instantiating UI elements for MacOS. The developer has also created classes MacOSButton and MacOSWindow that represent concrete prod-ucts that MacOSUIFactory instantiates when requested. However, the left win-dow in Figure 6.12 shows a situation where the application developer has acci-dentally changed the createButton method in MacOSUIFactory so that it instan-tiates and returns a wrong concrete class; instead of creating an instance of LinuxButton, it should instantiate MacOSButton. Role concreteCreate (in pattern UIFactory) indirectly inherits a class instantiation constraint from role method in pattern ClassInstantiation (see Appendix B, line 5093). That constraint is vio-lated, and the application developer gets notified by a reparation task: . By selecting the reparation task, the developer can view more detailed information about the constraint violation. After the violation has been fixed, the reparation task disappears.

The right-hand side of Figure 6.12 shows another constraint violation. This time the user has messed up the implements clause of class MacOSUIFactory; the class now tries to implement interface UFactory (which does not exist at all).

This causes three reparation tasks to appear: one for the invalid inheritance rela-tionship and two for the invalid overriding relarela-tionships between the methods in MacOSUIFactory and the method declarations in UIFactory.

For most constraint violations, there is an automatic fix that can be applied. In Figure 6.12 the developer has opened the context-sensitive menu for the

inheri-tance constraint violation task and is about to choose action Repair that triggers automatic constraint violation reparation. Repairing constraint violations auto-matically does not always produce the best possible results. For instance, this time the automatic reparation algorithm adds a new item UIElementFactory in the implements clause of class MacOSUIFactory. However, the non-existing in-terface UFactory is not removed from the implements clause, because we cannot know for sure that the developer wants to replace that interface name with the corrected one. It is, however, very easy for the developer to remove the unneces-sary name. The design principle for the automatic reparation heuristics has been:

do not make damage. That is, do not try to guess what the developer is thinking, if a wrong guess may have fatal consequences. Some constraint violations are not

do not make damage. That is, do not try to guess what the developer is thinking, if a wrong guess may have fatal consequences. Some constraint violations are not