The Object Teams Blog

Adding team spirit to your objects.

Posts Tagged ‘design

Follow-up: Object Teams Tutorial at EclipseCon 2011

leave a comment »

At our EclipseCon tutorial we mentioned a bonus excercise, for which we didn’t have the time at the tutorial.

Now it’s time to reveal the solution.

Task

Implement the following demo-mode for the JDT:

• When creating a Java project let the user select:
❒ Project is for demo purpose only
• When creating a class in a Demo project:
insert class name as “Foo1”, “Foo2” …”

So creating classes in demo mode is much easier, and you’ll use the names “Foo1″… anyway 🙂
(See also our slides (#39)).

Granted, this is a toy example, yet it combines a few properties that I frequently find in real life and which cause significant pains without OT/J:

  • The added behavior must tightly integrate with existing behavior.
  • The added behavior affects code at distant locations,
    here two plug-ins are affected: org.eclipse.jdt.ui and org.eclipse.jdt.core.
  • The added behavior affects execution at different points in time,
    here creation of a project plus creation of a class inside a project.
  • The added behavior requires to maintain more state at existing objects,
    here a JavaProject must remember if it is a demo project.

Despite these characteristics the task can be easily described in a few English sentences. So the solution should be similarly concise and delivered as a single coherent piece.

Strategy

With a little knowledge about the JDT the solution can be outlined as this

  • Add a checkbox to the New Java Project wizard
  • When the wizard creates the project mark it as a demo project if the box is checked.
  • Let the project also count the names of Foo.. classes it has created.
  • When the new class wizard creates a class inside a demo project pre-set the generated class name and make the name field unselectable.

From this we conclude the need to define 4 roles, playedBy these existing types:

  • org.eclipse.jdt.ui.wizards.NewJavaProjectWizardPageOne.NameGroup:
    the wizard page section where the project name is entered and where we want to add the checkbox.
  • org.eclipse.jdt.ui.wizards.NewJavaProjectWizardPageTwo:
    the part of the wizard that triggers setup of the JavaProject.
  • org.eclipse.jdt.core.IJavaProject:
    this is where we need to add more state (isDemoProject and numFooClasses).
  • org.eclipse.jdt.ui.wizards.NewTypeWizardPage:
    this is where the user normally specifies the name for a new class to be created.

Note, that 3 classes in this list resided in org.eclipse.jdt.ui, but IJavaProject is from org.eclipse.jdt.core, which leads us to the next step:

Plug-in configuration

Our solution is developed as an OT/Equinox plug-in, with the following architecture level connections:

This simply says that the same team demohelper.ProjectAdaptor is entitled to bind roles to classes from both org.eclipse.jdt.ui and org.eclipse.jdt.core.
One more detail in these extensions shouldn’t go unmentioned: Don’t forget to set “activation: ALL_THREADS” for the team (otherwise you won’t see any effect …).

Now we’re ready to do the coding.

Implementing the roles

protected class DialogExtender playedBy NameGroup {
 
        protected SelectionButtonDialogField isDemoField;
 
        void createControl(Composite parent) <- after Control createControl(Composite composite)
                with { parent <- (Composite) result }
 
        private void createControl(Composite parent) {
                isDemoField= new SelectionButtonDialogField(SWT.CHECK);
                isDemoField.setLabelText("Project is for demo purpose only");
                isDemoField.setSelection(false);
                isDemoField.doFillIntoGrid(parent, 4);
        }
}
 

Our first role adds the checkbox. The implementation of createControl is straight-forward UI business. Lines 22,23 hook our role method into the one from the bound base class NameGroup. After the with keyword, we are piping the result from the base method into the parameter parent of the role method (with a cast). This construct is a parameter mapping.

Here’s the result:

Next we want to store the demo-flag to instances of IJavaProject, so we write this role:

protected class EnhancedJavaProject playedBy IJavaProject {
 
        protected boolean isDemoProject;
        private int numFooClasses = 1;
 
        protected String getTypeName() {
                return "Foo"+(numFooClasses++);
        }
}
 

Great, now any IJavaProject can play the role EnhancedJavaProject which holds the two additional fields, and we can automatically serve an arbitrary number of class names Foo1 …
In the IDE you will actually see a warning, telling you that binding a role to a base interface currently imposes a few restrictions, but these don’t affect us in this example.

Next comes a typical question: how to transfer the flag from role DialogExtender to role EnhancedJavaProject?? The roles don’t know about each other nor do the bound base classes. The answer is: use a chain of references.

protected class FirstPage playedBy NewJavaProjectWizardPageOne {
 
        DialogExtender getFNameGroup() -> get NameGroup fNameGroup;
 
        protected boolean isDemoProject() {
                return getFNameGroup().isDemoField.isSelected();
        }
}
 
protected class WizardExtender playedBy NewJavaProjectWizardPageTwo {
 
        FirstPage getFFirstPage() -> get NewJavaProjectWizardPageOne fFirstPage;
 
        markDemoProject <- after initializeBuildPath;
        private void markDemoProject(EnhancedJavaProject javaProject) {
                if (getFFirstPage().isDemoProject())
                        javaProject.isDemoProject = true;
        }
}
 
 

Role WizardExtender intercepts the event when the wizard initializes the IJavaProject (line 46). Method initializedBuildPath receives a parameter of type IJavaProject but the OT/J runtime transparently translates this into an instance of type EnhancedJavaProject (this – statically type safe – operation is called lifting). Another indirection is needed to access the checkbox: The base objects are linked like this:

NewJavaProjectWizardPageTwo —fFirstPage—> NewJavaProjectWizardPageOne —fNameGroup—> NameGroup

This link structure is lifted to the role level by the callout bindings in lines 35 and 44.

We’re ready for our last role:

protected class NewTypeExtender playedBy NewTypeWizardPage {                                                              
                                                                                          
        void setTypeName(String name, boolean canBeModified) -> void setTypeName(String name, boolean canBeModified);                                                                      
                                                                                          
        void initTypePage(EnhancedJavaProject prj) <- after void initTypePage(IJavaElement element)
                with { prj <- element.getJavaProject() }
 
        private void initTypePage(EnhancedJavaProject prj) {
                if (prj.isDemoProject)
                        setTypeName(prj.getTypeName(), false);
        }
}
 

Here we intercept the initialization of the type page of a New Java Project wizard (lines 66,67). Another parameter mapping is used to perform two adjustments in one go: fetch the IJavaProject from the enclosing element and lift it to its EnhancedJavaProject role. This follows the rule-of-thumb that base-type operations (like navigating from IJavaElement to IJavaProject) should happen at the right hand side, so that we are ready to lift the IJavaProject to EnhancedJavaProject when the data flow enters the team.

The EnhancedJavaProject can now be asked for its stored flag (isDemoProject) and it can be asked for a generated class name (getTypeName()). The generated class name is then inserted into the dialog using the callout binding in line 64. Looks like this:

See this? No need to think of a good class name 🙂

Wrap-up

So that’s it. All these roles are collected in one team class and here is the fully expanded outline:

All this is indeed one concise and coherent module. In the tutorial I promised to do this no more than 80 LOC, and indeed the team class has 74 lines including imports and white space.

Or, if you are interested just in how this module connects to the existing implementation, you may use the “binding editor” in which you see all playedBy, callout and callin bindings:

The full sources are also available for download.

have fun

Advertisements

Written by Stephan Herrmann

April 14, 2011 at 21:14

Combination of Concerns

with one comment

If you are interested in more than two of these …

… you’ll surely enjoy this:

Hands-on introduction to Object Teams

See you at

Written by Stephan Herrmann

March 17, 2011 at 16:52