2009年9月11日星期五

Smooks tools – Generate GUIs for attributes of EMF model

Overview

In the early version , the Smooks configuration editor is a multipe-page editor. Except a Text page , the other page is a Form page base on Master-Details editor , we call it “Design Page”.

Smooks tools use the EMF to create the related EMF model via the XSD files come from Smooks , all of them put in the org.jboss.tools.smooks.core plug-in project.

This is the “Design Page” :

smookstools_message_filter_page

On the left-side , there is a TreeViewer , right-side panel displays the GUIs.

The input models for the left-side TreeViewer are the EMF models what are loaded from the Smooks configuration file by EMF Resource , all of the tree nodes are EMF model ( or the warp model provide by emf.edit) , the right-side panel show the GUIs for the attributes of EMF model which is selected on.

The guys who are familiar with the Eclipse plguin development should know that if we developed the Master-Details type FromPage , we need to regist the model type and the GUI class related to the mode , so when the selection changed from the Master , the Details will get the GUI via the new model type and re-create the GUI.

Although the Smooks configuration editor use the Master-Details , it didn’t regist the different GUI for every different model , it regist the GUI like this :

detailsPart.registerPage(EObject.class, new SmooksStuffPropertyDetailPage((SmooksMultiFormEditor) this.formEditor));

It use the EObject as the model type , it means that , when the left-side TreeViewer fire the “selection change event” , the Details page can only the same GUI instance evenrytime , and this GUI class is : org.jboss.tools.smooks.configuration.editors.SmooksStuffPropertyDetailPage

But it dosen’t mean that the Details page can’t show the different GUI for different model. Details page has a “selectionChange” method , this method will be called when the selection was changed in Master page.

This is the implemention for the method of SmooksStuffPropertyDetailPage :

public void selectionChanged(IFormPart part, ISelection selection) {
    Object oldModel = getModel();
    setOldModel(oldModel);
    …………
    this.itemPropertySource = (IItemPropertySource) editingDomain.getAdapterFactory().adapt(getModel(),
            IItemPropertySource.class);
      ………
      ………
    if (getOldModel() != getModel()) {
        if (propertyComposite != null) {
            propertyComposite.dispose(); // remove the old properties’ GUI
            propertyComposite = new Composite(propertyMainComposite, SWT.NONE);
        }
        createStuffDetailsComposite(propertyComposite); // recreate the GUI for new model
    }
}

We can know that from the codes : When the model selection was changed , if it’s a new model (different with the current model) , the Composite which displayed the GUIs for the model attributes will be disposed , and then create a new Composite instance and create new attributes GUI via the new model.

How to create attributes GUI via EMF model

We know that , emf.edit can generate a ******ItemProvider for each EMF model , and generate a ******ItemProviderFactory to manage those ItemProvider.

The ItemProvider has many functions , it implements many interface for common GUI display , such as : IItemLabelProvider (ILabelProvider) , IITreeItemContentProvider ( IStructuredContentProvider) , IItemPropertySource ( PropertySource). One of them , the IItemPropertySource can get the attributes’ informations from the EMF model.

When initialize the Smooks configuration editor , I create an “AdapterFactoryEditingDomain” , and registe those ProviderFactory into this EditingDomain , so the ItemProvider can be get from the EditingDomain : ( in the AbstractSmooksFormEditor , line 223)

protected void initEditingDomain() {
    adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
    // add smooks 1.1.2 EMF item provider
    adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
    .........
    // add smooks 1.2 EMF item provider
    adapterFactory.addAdapterFactory(new Json12ItemProviderAdapterFactory());
    .........
    BasicCommandStack commandStack = new BasicCommandStack();
    handleCommandStack(commandStack);
    editingDomain = new AdapterFactoryEditingDomain(adapterFactory, commandStack, new HashMap<Resource, Boolean>());
}

SmooksStuffPropertyDetailPage just use the ItemPropertySource to get the Descriptor of each property, and use those Descriptors to generate the related GUIs.

There are two ways for generating Properties’ GUI:

1. Generate the default Property GUI. This way will create a default GUI for each property.

2. Generate the Property GUI by PropertyUICreator. Use the PropertyUICreator to generate the properties’ GUI , if it generate NULL for the properties , SmooksStuffPropertyDetailPage will use the first way to generate the GUI again.

What’s the PropertyUICreator?

PropertyUICreator is a interface : org.jboss.tools.smooks.configuration.editors.IPropertyUICreator , If implement the interface and registe the mode type and the related PropertyUICreator instance into the PropertyUICreatorManager , SmooksStuffPropertyDetailPage can get the IPropertyUICreator instance via each type model.

Let’s return to the codes , there is a method : “createStuffDetailsComposite” in the SmooksStuffPropertyDetailPage :

protected void createStuffDetailsComposite(Composite detailsComposite) {
    try {
        .......

        // Get the PropertyUICreator instance
        IPropertyUICreator creator = PropertyUICreatorManager.getInstance().getPropertyUICreator(getModel());
        // Get the Property Descriptor from IItemPropertySource instance.
        List<IItemPropertyDescriptor> propertyDes = itemPropertySource.getPropertyDescriptors(getModel());
        if (creator != null) {
            // Create the Property Extention-GUI via PropertyUICreator
        }

        for (int i = 0; i < propertyDes.size(); i++) {
            IItemPropertyDescriptor pd = propertyDes.get(i);
            EAttribute attribute = (EAttribute) pd.getFeature(getModel());
            if (attribute.isRequired()) {
                // Create the required attribute GUI first
                AttributeFieldEditPart editPart = createAttributeUI(detailsComposite, pd, creator);
                ........
            }
        }
        for (int i = 0; i < propertyDes.size(); i++) {
            IItemPropertyDescriptor pd = propertyDes.get(i);
            EAttribute attribute = (EAttribute) pd.getFeature(getModel());
            if (!attribute.isRequired()) {
                // Create the un-required attribute GUI
                AttributeFieldEditPart editPart = createAttributeUI(detailsComposite, pd, creator);
                .......
            }
        }
        if (creator != null) {
            // Create attribute GUI via PropertyUICreator
        }
        ........
    } catch (Exception e) {
        // ignore
    }
}

 

If the codes is hard to understand , maybe the flow diagram can help you :

myImage

 

Generate GUI via PropertyDescriptor

There is a method in SmooksStuffPropertyDetailPage , its duty is generate the GUI via IPropertyDescriptor instance.

protected AttributeFieldEditPart createAttributeUI(Composite detailsComposite,
        IItemPropertyDescriptor propertyDescriptor, IPropertyUICreator creator) {
    final IItemPropertyDescriptor itemPropertyDescriptor = propertyDescriptor;
    EAttribute feature = (EAttribute) itemPropertyDescriptor.getFeature(getModel());
    AttributeFieldEditPart editPart = null;
    if (createDefault) {
        EClassifier typeClazz = feature.getEType();
        boolean hasCreated = false;
        if (typeClazz instanceof EEnum) {
            ....
        }
        if (typeClazz.getInstanceClass() == String.class) {
            ......
        }
        ......
    }

    return editPart;
}

It’s not hard to see , get the Class type and generate different GUI with this type. In the SmooksUIUtils class , we can see how to generate the GUI.

When generate the GUI , SmooksUIUtils will add a Listener for the GUI control to monitor the modification of GUI controls , when the GUI control was modified , the properties’ value will be change.

Let’s look a fragment of SmooksUIUtils : (from line 945:)

     if (itemPropertyDescriptor != null) {
            // add modify listener to the ValueText control
            valueText.addModifyListener(new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    // Get the value of this property
                    Object editValue = getEditValue(itemPropertyDescriptor, fm);
                    if (editValue != null) {
                        String vt = valueText.getText();
                        if (vt == null || vt.length() == 0) {
                            // if the text is null , un-set the property value.
                            itemPropertyDescriptor.setPropertyValue(fm, null);
                        } else {
                            // set the new value to this property
                            if (!editValue.equals(vt)) {
                                itemPropertyDescriptor.setPropertyValue(fm, vt);
                            }
                        }
                    } else {
                        // set the new value to this property
                        itemPropertyDescriptor.setPropertyValue(fm, valueText.getText());
                    }

                }
            });
        }

ModelPanelCreator

Actually , I copy the codes which generate the property GUI form the SmooksStuffPropertyDetailPage and create a new Class : ModelPanelCreator. So I can generate the EMF model attributes’ GUI everywhere.

Those class use the ModelPanelCreator:

org.jboss.tools.smooks.configuration.editors.NewOrModifySmooksElementDialog

org.jboss.tools.smooks.configuration.editors.SmooksConfigurationOverviewPage

org.jboss.tools.smooks.edimap.editors.EDIMapFormPage

Smooks tools – 通过EMF模型生成其属性GUI

概述

Smooks configuration editor 在早期的版本是一个具有2个页面的多页编辑器。除了其中一个文本页,另外一个页面是一个基于Master-Details的编辑器,称为Design Page。

Smooks configuration editor 是利用EMF,通过Smooks所定义的配置文件的XSD创建对应的EMF模型,所有的EMF模型相关代码都放在了org.jboss.tools.smooks.core 插件项目中。

在上面所提到的设计页面如下图所示:

smookstools_message_filter_page

该页面左侧是一个TreeViewer,右侧则在选中左侧Viewer中的item后显示其属性编辑界面。

而左侧TreeViewer的输入模型正是通过EMF Resource读取Smooks配置文件后得到的EMF模型,其中所有的节点都是EMF模型(或者被emf.edit包装过的模型),右侧的Details界面显示了被选中EMF模型的属性。

熟悉Eclipse开发的读者应该知道,在创建Master-Details类型的FormPage时,我们需要把模型类型以及它所对应的GUI注册一遍,这样一来,当Master的Selection改变的时候,它才能够利用新的模型类型取出对应的GUI,然后重新创建Details的GUI。

Smooks configuration editor 虽然也是使用Master-Details,但它并没有为每一个模型注册对应的GUI页面,而是用这些模型的共同的父类:EObject作为模型类型进行注册,这意味着,每当左侧TreeViewer发生了SelectionChange后,Details页面每次都只能从注册的页面中得到同一个GUI对象,这个GUI对象就是:org.jboss.tools.smooks.configuration.editors.SmooksStuffPropertyDetailPage

但这并不意味着左侧的Details页面不会根据不同模型而改变。Details页面有一个selectionChange的方法,是当模型选择发生改变后触发的。SmooksStuffPropertyDetailPage实现了这个方法:

public void selectionChanged(IFormPart part, ISelection selection) {
    Object oldModel = getModel();
    setOldModel(oldModel);
    …………
    this.itemPropertySource = (IItemPropertySource) editingDomain.getAdapterFactory().adapt(getModel(),
            IItemPropertySource.class);
      ………
      ………
    if (getOldModel() != getModel()) {
        if (propertyComposite != null) {
            propertyComposite.dispose(); // remove the old properties’ GUI
            propertyComposite = new Composite(propertyMainComposite, SWT.NONE);
        }
        createStuffDetailsComposite(propertyComposite); // recreate the GUI for new model
    }
}

从代码中可以看出,每当模型改变的时候,如果是一个新的模型,而不是当前模型,那显示模型属性的Composite会被dispose掉,然后重新创建一个新的Composite对象,再根据新模型创建新的模型属性GUI。

如何根据EMF模型创建属性GUI.

我们都知道,emf.edit会为每一个EMF模型生成一个XXXXXXXItemProvider,然后生成一个XXXXItemProviderFactory的类,来维护为这些ItemProvider。

这些ItemProvider具有很多功能,它实现了很多常见的为GUI显示准备的接口,比如:IItemLabelProvider, ITreeItemContentProvider , IItemPropertySource。其中IItemPropertySource正是获取模型属性的接口。

Smooks configuration editor在初始化的时候,创建了AdapterFactoryEditingDomain,并把这些ProviderFactory注册到这个EditingDomain中,这样一来,就可以通过EditingDomain获得对应的XXXItemProvider。

SmooksStuffPropertyDetailPage正是通过ItemPropertySource获得每一个属性的Descriptor,然后根据这些Descriptor来创建出对应的GUI。

创建Property的GUI分成两种形式:

1. 创建默认的Property GUI。这种方法会为每一个对应的属性模型创建出默认的GUI。

2. 使用注册好的PropertyUICreator创建。通过PropertyUICreator来创建出属性的GUI,如果Creator所创建出的GUI为NULL,SmooksStuffPropertyDetailPage会通过第一种方法创建出GUI来。

PropertyUICreator是一个接口:org.jboss.tools.smooks.configuration.editors.IPropertyUICreator,只要实现了这个接口,并把模型类型和该接口的实力注册到单态类PropertyUICreatorManager中,SmooksStuffPropertyDetailPage就能根据模型类型获得IPropertyUICreator的实例。

现在回到代码中,在SmooksStuffPropertyDetailPage中有一个createStuffDetailsComposite的方法:

protected void createStuffDetailsComposite(Composite detailsComposite) {
    try {
        .......

        // Get the PropertyUICreator instance
        IPropertyUICreator creator = PropertyUICreatorManager.getInstance().getPropertyUICreator(getModel());
        // Get the Property Descriptor from IItemPropertySource instance.
        List<IItemPropertyDescriptor> propertyDes = itemPropertySource.getPropertyDescriptors(getModel());
        if (creator != null) {
            // Create the Property Extention-GUI via PropertyUICreator
        }

        for (int i = 0; i < propertyDes.size(); i++) {
            IItemPropertyDescriptor pd = propertyDes.get(i);
            EAttribute attribute = (EAttribute) pd.getFeature(getModel());
            if (attribute.isRequired()) {
                // Create the required attribute GUI first
                AttributeFieldEditPart editPart = createAttributeUI(detailsComposite, pd, creator);
                ........
            }
        }
        for (int i = 0; i < propertyDes.size(); i++) {
            IItemPropertyDescriptor pd = propertyDes.get(i);
            EAttribute attribute = (EAttribute) pd.getFeature(getModel());
            if (!attribute.isRequired()) {
                // Create the un-required attribute GUI
                AttributeFieldEditPart editPart = createAttributeUI(detailsComposite, pd, creator);
                .......
            }
        }
        if (creator != null) {
            // Create attribute GUI via PropertyUICreator
        }
        ........
    } catch (Exception e) {
        // ignore
    }
}

 

如果从代码中不容易看出整个创建流程,下图能帮助你去理解:

myImage

 

根据PropertyDescriptor创建GUI

在SmooksStuffPropertyDetailPage中有一个方法,是负责根据IPropertyDescriptor创建GUI的。

protected AttributeFieldEditPart createAttributeUI(Composite detailsComposite,
        IItemPropertyDescriptor propertyDescriptor, IPropertyUICreator creator) {
    final IItemPropertyDescriptor itemPropertyDescriptor = propertyDescriptor;
    EAttribute feature = (EAttribute) itemPropertyDescriptor.getFeature(getModel());
    AttributeFieldEditPart editPart = null;
    if (createDefault) {
        EClassifier typeClazz = feature.getEType();
        boolean hasCreated = false;
        if (typeClazz instanceof EEnum) {
            ....
        }
        if (typeClazz.getInstanceClass() == String.class) {
            ......
        }
        ......
    }

    return editPart;
}

不难看出,根据输入Property的Class类型,创建出不同的GUI。而创建这些对应GUI的具体实现,是在 SmooksUIUtils中实现的。

值得一提的是,所创建出来的GUI,都会自动添加监听器,去监听GUI的变化(比如,文本框的变化),然后根据变化将值回写到Property中,我们看SmooksUIUtils中一个代码片段 (from line 945 ) :

     if (itemPropertyDescriptor != null) {
            // add modify listener to the ValueText control
            valueText.addModifyListener(new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    // Get the value of this property
                    Object editValue = getEditValue(itemPropertyDescriptor, fm);
                    if (editValue != null) {
                        String vt = valueText.getText();
                        if (vt == null || vt.length() == 0) {
                            // if the text is null , un-set the property value.
                            itemPropertyDescriptor.setPropertyValue(fm, null);
                        } else {
                            // set the new value to this property
                            if (!editValue.equals(vt)) {
                                itemPropertyDescriptor.setPropertyValue(fm, vt);
                            }
                        }
                    } else {
                        // set the new value to this property
                        itemPropertyDescriptor.setPropertyValue(fm, valueText.getText());
                    }

                }
            });
        }

ModelPanelCreator

ModelPanelCreator其实是将SmooksStuffPropertyDetailPage中创建属性GUI的代码复制了出来,实现了一个独立的类,这样就可以在其他的非Master-Details的GUI中使用了。

该类主要应用可以参考:

org.jboss.tools.smooks.configuration.editors.NewOrModifySmooksElementDialog

org.jboss.tools.smooks.configuration.editors.SmooksConfigurationOverviewPage

org.jboss.tools.smooks.edimap.editors.EDIMapFormPage