SDA SE Wiki

Software Engineering for Smart Data Analytics & Smart Data Analytics for Software Engineering

User Tools

Site Tools


Context

Nomad PIM (in the current, not-yet-released version) uses a service that returns the current date context of the application and notifies interest listeners on changes. The idea of that is that all date related information like the one in schedule views, calendar, title bar, evaluation views is refreshed based on the currently selected date. For example, if you choose a certain date in the calendar view, the schedule view should show only information for that date (or a timespan that date lies in, like a week or a month) and the title bar should be refreshed accordingly.

State before the refactoring

Calling Code and Interface

For the refactoring, I will restrict myself to the views that implement IDateServiceListener. These views are called on changes by the following code snippet:

        IViewReference[] viewReferences = PlatformUI.getWorkbench()
                .getActiveWorkbenchWindow().getActivePage().getViewReferences();

        for (IViewReference reference : viewReferences) {
            IViewPart view = reference.getView(false);
            if (view != null) {
                final IDateServiceListener adapter = (IDateServiceListener) view
                        .getAdapter(IDateServiceListener.class);

                if (adapter != null) {
                    Display.getCurrent().asyncExec(new Runnable() {                    
                        public void run() {
                            adapter.dateChange(newDate, source);
                        }                    
                    });
                }
            }
        }

Basically, it searchs for all currently available views that implement IDateServiceListener either directly or indirectly (using adapter factories). If they implement the interface directly, adapter == view holds because of the implementation of getAdapter().

Interface IDateServiceListener




public interface IDateServiceListener {



    void dateChang

(Date newDate, Object source);

    

}

Views

Here is a short list of the views that implement IDateServiceListener:

  • CalendarView
  • DayView
  • PastEventsView
  • ScheduleView
  • TimeEvaluationView
  • WeekOverviewView

The implementations all differ, many have weaknesses caused by the inconsistent implementation of time. For example, some views have link buttons to let the user select whether they should be updated on changes or not, whereas other views do not have such a button.

The refactoring should also improve the quality of the code by eliminating these weaknesses (one could perhaps argue that it's therefore not really a refactoring).

Relevant code from TimeEvaluationView




public class TimeEvaluationView extends ViewPart implements

        IDateServiceListener {



    private Date currentDate;



    private LinkAction linkAction;



    public void dateChange(Date newDate, Object source) {

        if (currentDate != null

                && new SameWeekFilter(currentDate).evaluate(newDate)) {

            return;

        }

        

        if (!linkAction.isChecked()) {

            return;

        }



        currentDate = newDate;



        refresh();

    }



...



}

Relevant code from ScheduleView




public class ScheduleView extends ViewPart implements

        IDateServiceListener {



    private LinkAction linkAction;



    public void dateChange(Date newDate, Object source) {

        if (!linkAction.isChecked()) {

            return;

        }



        getAction(SCHEDULE_VIEW_FILTER_DAY).changeFilter(

                new DateFilterToInformationFilterAdapter(new SameDayFilter(

                        newDate)));

        getAction(SCHEDULE_VIEW_FILTER_WEEK).changeFilter(

                new DateFilterToInformationFilterAdapter(new SameWeekFilter(

                        newDate)));

        getAction(SCHEDULE_VIEW_FILTER_MONTH).changeFilter(

                new DateFilterToInformationFilterAdapter(new SameMonthFilter(

                        newDate)));

    }



...



}



Relevant code from DayView




public class DayView extends ViewPart implements

        IDateServiceListener {



    public void dateChange(Date newDate, Object source) {

        setModel(DiaryUtil.getDay(newDate));

    }



...



}



Relevant code from WeekOverviewView




public class WeekOverviewView extends ViewPart implements

        IDateServiceListener {



    private Date date;



    private LinkAction linkAction;



    public void dateChange(Date newDate, Object source) {

        setDate(newDate);

    }



    private void setDate(Date newDate) {

        assert newDate != null;



        if (!linkAction.isChecked()) {

            return;

        }

        

        Date newWeekDate = dateConverter.convert(newDate);



        if (newWeekDate.equals(date)) {

            return;

        }

        

        date = newWeekDate;



        viewer.setInput(date);

    }



...



}



Relevant code from CalendarView




public class TimeEvaluationView extends ViewPart implements

        IDateServiceListener {



    public void dateChange(Date newDate, Object source) {

        if (source == this) {

            return;

        }



        Calendar calendar = Calendar.getInstance();

        calendar.setTime(newDate);

        swtCalendar.setCalendar(calendar);

    }



...



}



Relevant code from PastEventsView




public class PastEventsView extends ViewPart implements

        IDateServiceListener {



    private Date date = new Date();



    public void setDate(Date newDate) {

        this.date = newDate;

        update();

    }



...



}



Steps

1. Introduce a tagging annotation

create the annotation




import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;



@Retention(RetentionPolicy.RUNTIME)

@Target( { ElementType.TYPE })

@Documented

public @interface DateServiceUser {

    

}



apply it to the views (example)




@DateServiceUser

public class TimeEvaluationView extends ViewPart implements

        IDateServiceListener



2. Create an aspect that lifts the interface implementation declaration to the aspect

create the aspect

public aspect DateServiceUserAspect {
    declare parents: (@DateServiceUser *) extends IDateServiceListener;
}

remove the direct implementation declaration from the views (example)




@DateServiceUser

public class TimeEvaluationView extends ViewPart 



3. Lifting the source check up to the aspect by introducing another interface in the aspect

4. Minor changes

moving assertions into the aspect

inlining some not-so-relavant-methods

5. Moving the current date and some checks to the aspect

aspect

changes in the classes (example: TimeEvaluationView)




    public boolean isDateChanged(Date newDate) {

        return !new SameWeekFilter(getCurrentDate()).evaluate(newDate);

    }



    public void dateChange(Date newDate) {

        if (!linkAction.isChecked()) {

            return;

        }



        refresh();

    }



At this state, the code is somewhat broken because the check of the link action is executed after changing the current date. Step 6 solves this.




@Retention(RetentionPolicy.RUNTIME)

@Target( { ElementType.TYPE })

@Documented

public @interface DateServiceUserWithLinkAction {

    

}



Adding an additional interface to the aspect and extend the marked classes with that interface




@DateServiceUserWithLinkAction

public class ScheduleView extends ViewPart






    protected interface IDateServiceListenerWithLinkActionAspectInterface

            extends IDateServiceListenerAspectInterface {

        

        LinkAction getLinkAction();

        

    }



Implementation in ScheduleView (Example):




    public LinkAction getLinkAction() {

        return linkAction;

    }

  




    public void dateChange(Date newDate) {

        refresh();

    }

   

State after the refactoring

Annotations




@Retention(RetentionPolicy.RUNTIME)

@Target( { ElementType.TYPE })

@Documented

public @interface DateServiceUser {

    

}

   




@Retention(RetentionPolicy.RUNTIME)

@Target( { ElementType.TYPE })

@Documented

public @interface DateServiceUserWithLinkAction {

    

}

   

Aspect

{Java2HtmlPlugin public aspect DateServiceUserAspect { protected interface IDateServiceListenerAspectInterface extends IDateServiceListener { void dateChange(Date newDate); Date getCurrentDate(); boolean isDateChanged(Date newDate); } declare parents: (@DateServiceUser *) extends IDateServiceListenerAspectInterface; public void IDateServiceListenerAspectInterface.dateChange(Date newDate, Object source) { assert newDate != null; if (this == source) { return; } if (currentDate != null && !isDateChanged(newDate)) { return; } currentDate = newDate; dateChange(newDate); } private Date IDateServiceListenerAspectInterface.currentDate; public Date IDateServiceListenerAspectInterface.getCurrentDate() { return currentDate; } // default implementation public boolean IDateServiceListenerAspectInterface.isDateChanged( Date newDate) { return true; } protected interface IDateServiceListenerWithLinkActionAspectInterface extends IDateServiceListenerAspectInterface { LinkAction getLinkAction(); } declare parents: (@DateServiceUserWithLinkAction *) extends IDateServiceListenerWithLinkActionAspectInterface; void around(Date newDate, Object source, IDateServiceListenerAspectInterface targetObject): execution(void IDateServiceListenerAspectInterface.dateChange(Date, Object)) && args(newDate,source) && target(targetObject) { if (targetObject instanceof IDateServiceListenerWithLinkActionAspectInterface) { LinkAction linkAction = ((IDateServiceListenerWithLinkActionAspectInterface) targetObject) .getLinkAction(); if (!linkAction.isChecked()) { return; } } proceed(newDate, source, targetObject); } } }

Classes (Views)

Relevant code from TimeEvaluationView




@DateServiceUserWithLinkAction

public class TimeEvaluationView extends ViewPart {



    private LinkAction linkAction;



    public LinkAction getLinkAction() {

        return linkAction;

    }

   

    public boolean isDateChanged(Date newDate) {

        return !new SameWeekFilter(getCurrentDate()).evaluate(newDate);

    }



    public void dateChange(Date newDate) {

        refresh();

    }



...



}

   

Relevant code from ScheduleView




@DateServiceUserWithLinkAction

public class ScheduleView extends ViewPart {



    private LinkAction linkAction;



    public LinkAction getLinkAction() {

        return linkAction;

    }



    public void dateChange(Date newDate) {

        getAction(SCHEDULE_VIEW_FILTER_DAY).changeFilter(

                new DateFilterToInformationFilterAdapter(new SameDayFilter(

                        newDate)));

        getAction(SCHEDULE_VIEW_FILTER_WEEK).changeFilter(

                new DateFilterToInformationFilterAdapter(new SameWeekFilter(

                        newDate)));

        getAction(SCHEDULE_VIEW_FILTER_MONTH).changeFilter(

                new DateFilterToInformationFilterAdapter(new SameMonthFilter(

                        newDate)));

    }



...



}

   

Relevant code from DayView




@DateServiceUser

public class DayView extends ViewPart {



    public void dateChange(Date newDate) {

        setModel(DiaryUtil.getDay(newDate));

    }



...



}

   

Relevant code from WeekOverviewView




@DateServiceUserWithLinkAction

public class WeekOverviewView extends ViewPart {



    private LinkAction linkAction;



    public LinkAction getLinkAction() {

        return linkAction;

    }

   

    public boolean isDateChanged(Date newDate) {

        return !new SameWeekFilter(getCurrentDate()).evaluate(newDate);

    }



    public void dateChange(Date newDate) {

        viewer.setInput(dateConverter.convert(newDate));

    }



...



}



Relevant code from CalendarView




@DateServiceUser

public class CalendarView extends ViewPart {



    public void dateChange(Date newDate) {

        Calendar calendar = Calendar.getInstance();

        calendar.setTime(newDate);

        swtCalendar.setCalendar(calendar);

    }



...



}



Relevant code from PastEventsView




@DateServiceUser

public class PastEventsView extends ViewPart {



    public void dateChange(Date newDate) {

        update();

    }



...



}



Evaluation of the result

consistency

A lot of consistency was gained by moving the checks into the aspect. For example, now, it is always checked whether the source is the same as the current object before change the date. Additionally, the complexity in the view classes was reduced because the only see what they need to (for example not the source of the date change).

reusability

The aspect and the annotion classes are reusable. The effort to implement new views which change their values based on the selected date is expected to be reduced a lot, and the quality of these implementations is expected to be higher, because the checks are contained in the aspect that is reused.

complexity

The complexity of using the date selection is reduced, because it comes down to adding an annotation and implementing some methods required by the aspect interfaces.

But the complexity of changing the aspect is higher with the new solution. This is because the aspect has to handle all cases of classes, not just one as before. The advantage is that once the aspect is changed, the changes apply to all @DateServiceUser marked classes.

comprehensibility

The new solution is harder to understand, at least at first. I expect that is will be easier once one is used to such a pattern, because the amount of directly visible information is reduced in the @DateServiceUser marked classes.

Further Work

Annotation attributation and inheritance

The solution with two different annotations is not very elegant. Using annotation attributes or annotation inheritance (is that possible?) would be better.

The link action could be moved more or less completly to the aspect, which would be a lot better.

Compare to multiple inheritance

The result smells like using aspects and annotations to gain some sort of multiple inheritance to me. Multiple inheritance was not included in Java for good? reasons. So it would be interesting what kind of inheritance is used here (–> B.Meyer) and how it can be compared to multiple inheritance.

Remarks

Weaving aspects from required plugins in eclipse using multiple projects

Use “(Project) Properties » AspectJ Aspect Path » Add class folder” to add the class folder containing the aspects to be woven.

research/cultivate/refactoringtoaspectsexamplenomadpimidateservicelistener.txt · Last modified: 2018/05/09 01:59 (external edit)

SEWiki, © 2019