posts - 36, comments - 30, trackbacks - 0, articles - 3

流程设计器开发一(模型部分)

Posted on 2008-01-02 15:32 笑看人生 阅读(3504) 评论(5)  编辑  收藏 所属分类: Java插件开发
    我自从进入公司后,一直从事有关gef方面的开发工作,在这期间,走过不少弯路,仅仅是把GEF框架弄明白,就费了很大力气,所以,现在想写一点东西出来,供初学者阅读。

    GEF(Graphical Editing Framework)是图形化编辑器开发的工具,比较典型的应用就是IBM 的Rose,它是一个模型驱动的MVC框架,控制器(EditPart)作为模型的侦听器,侦听模型的变化,如果模型的属性发生变化,它会通知控制器,控制器就会刷新模型对应的视图(Figure)。可以看出模型和视图之间没有直接联系,它们通过控制器而间接联系,可见控制器在gef框架中有着很重要的重要。

    下面我们将从最基本的开始,介绍如何用GEF框架开发出一个流程设计器(开发工具Eclipse3.2.1包含插件包gef3.2.1和draw2d3.2.1)。

    我们首先从模型开始,流程设计器顶层模型是流程(WorkflowProcess),流程有活动和链接这些活动的转移组成,其中活动又可以分为开始活动,普通活动,结束活动。理解了模型之间的组成关系,我们就可以设计模型对应的类了。由于上面提到,模型的属性变化了,必须通知控制器,由它来刷新模型对应的视图,所以控制器必须注册为模型的侦听器。由于每个模型都有相应的控制器侦听器侦听它属性的变化,我们把这方面的功能都放在父类中,定义一个ModelElement父类,具体代码如下:

 1package com.example.workflow.model;
 2 
 3import java.beans.PropertyChangeListener;
 4import java.beans.PropertyChangeSupport;
 5import java.io.IOException;
 6import java.io.ObjectInputStream;
 7import java.io.Serializable;
 8publicclass ModelElement implements Serializable{   
 9    privatestaticfinallongserialVersionUID = -5117340723140888394L;   
10    privatetransient PropertyChangeSupport pcsDelegate = new PropertyChangeSupport(this);
11       
12    publicsynchronizedvoid addPropertyChangeListener(PropertyChangeListener l) {
13       if (l == null{
14           thrownew IllegalArgumentException();
15       }

16       pcsDelegate.addPropertyChangeListener(l);
17    }

18       
19    protectedvoid firePropertyChange(String property, Object oldValue, Object newValue) {
20       if (pcsDelegate.hasListeners(property)) {
21           pcsDelegate.firePropertyChange(property, oldValue, newValue);
22       }

23    }

24       
25    privatevoid readObject(ObjectInputStream in) 
26    throws IOException, ClassNotFoundException {
27       in.defaultReadObject();
28       pcsDelegate = new PropertyChangeSupport(this);
29    }

30    
31    publicsynchronizedvoid removePropertyChangeListener(PropertyChangeListener l) {
32       if (l != null{
33           pcsDelegate.removePropertyChangeListener(l);
34       }

35    }

36 
37}

38

接下来我们定义流程,活动,转移模型,让这些模型都继承这个父类ModelElement,我们注意到活动由开始活动,普通活动,结束活动组成,这三类活动由很多相同的属性,例如活动的位置,名称,大小等等,所以给这三类活动进行抽象,定义一个父类AbstractActivity,把这些公共属性都放在这个父类中,父类的代码如下:

  1package com.example.workflow.model;
  2 
  3import java.util.ArrayList;
  4import java.util.List;
  5 
  6import org.eclipse.draw2d.geometry.Dimension;
  7import org.eclipse.draw2d.geometry.Point;
  8 
  9/**
 10 * Abstract prototype of a Activity.
 11 * Has a size (width and height), a location (x and y position) and a list of incoming
 12 * and outgoing connections. Use subclasses to instantiate a specific Activity.
 13 * @see com.example.workflow.model.Activity
 14 * @see com.example.workflow.model.StartActivity
 15 * @see com.example.workflow.model.EndActivity
 16 */

 17public class AbstractActivity extends ModelElement{
 18    
 19    private static final long serialVersionUID = 3023802629976246906L
 20    /** Property ID to use when the location of this Activity is modified. */
 21    public static final String LOCATION_PROP = "Activity.Location";
 22    /** Property ID to use then the size of this Activity is modified. */
 23    public static final String SIZE_PROP = "Activity.Size";
 24    /** ID for the Name property value (used for by the corresponding property descriptor). */
 25    public static final String NAME_PROP = "Activity.Name";
 26    
 27    /** Property ID to use when the list of outgoing transitions is modified. */
 28    public static final String SOURCE_TRANSITIONS_PROP = "Activity.SourceTran";
 29    /** Property ID to use when the list of incoming transitions is modified. */
 30    public static final String TARGET_TRANSITIONS_PROP = "Activity.TargetTran";
 31    /** ID for the Width property value (used for by the corresponding property descriptor). */
 32    private static final String WIDTH_PROP = "Activity.Width";
 33    /** ID for the X property value (used for by the corresponding property descriptor). */
 34    private static final String XPOS_PROP = "Activity.xPos";
 35    /** ID for the Y property value (used for by the corresponding property descriptor). */
 36    private static final String YPOS_PROP = "Activity.yPos";
 37    
 38    
 39    /** Name of this Activity. */
 40    private String name = new String("");
 41    /** Location of this Activity. */
 42    private Point location = new Point(00);
 43    /** Size of this Activity. */
 44    private Dimension size = new Dimension(5050);
 45    /** List of outgoing Transitions. */
 46    private List sourceTransitions = new ArrayList();
 47    /** List of incoming Transitions. */
 48    private List targetTransitions = new ArrayList();
 49    
 50    /**
 51     * Add an incoming or outgoing connection to this Activity.
 52     * @param conn a non-null Transition instance
 53     * @throws IllegalArgumentException if the Transition is null or has not distinct endpoints
 54     */

 55    void addTransition(Transition tran) {
 56       if (tran == null || tran.getSource() == tran.getTarget()) {
 57           throw new IllegalArgumentException();
 58       }

 59       if (tran.getSource() == this{
 60           sourceTransitions.add(tran);
 61           firePropertyChange(SOURCE_TRANSITIONS_PROP, null, tran);
 62       }
 else if (tran.getTarget() == this{
 63           targetTransitions.add(tran);
 64           firePropertyChange(TARGET_TRANSITIONS_PROP, null, tran);
 65       }

 66    }

 67    
 68    /**
 69     * Return the Name of this Activity.
 70     * @return name
 71     */

 72    public String getName() {
 73       return name;
 74    }

 75    
 76    /**
 77     * Return the Location of this Activity.
 78     * @return a non-null location instance
 79     */

 80    public Point getLocation() {
 81       return location.getCopy();
 82    }
   
 83    
 84    /**
 85     * Return the Size of this Activity.
 86     * @return a non-null Dimension instance
 87     */

 88    public Dimension getSize() {
 89       return size.getCopy();
 90    }

 91 
 92    /**
 93     * Return a List of outgoing Transitions.
 94     */

 95    public List getSourceTransitions() {
 96       return new ArrayList(sourceTransitions);
 97    }

 98 
 99    /**
100     * Return a List of incoming Transitions.
101     */

102    public List getTargetTransitions() {
103       return new ArrayList(targetTransitions);
104    }

105    
106    /**
107     * Remove an incoming or outgoing Transition from this Activity.
108     * @param conn a non-null Transition instance
109     * @throws IllegalArgumentException if the parameter is null
110     */

111    void removeTransition(Transition tran) {
112       if (tran == null{
113           throw new IllegalArgumentException();
114       }

115       if (tran.getSource() == this{
116           sourceTransitions.remove(tran);
117           firePropertyChange(SOURCE_TRANSITIONS_PROP, null, tran);
118       }
 else if (tran.getTarget() == this{
119           targetTransitions.remove(tran);
120           firePropertyChange(TARGET_TRANSITIONS_PROP, null, tran);
121       }

122    }

123    
124    /**
125     * Set the Name of this Activity.
126     * @param newName
127     * @throws IllegalArgumentException if the parameter is null
128     */

129    public void setName(String newName) {
130       if (newName == null{
131           throw new IllegalArgumentException();
132       }

133       this.name = newName;
134       firePropertyChange(LOCATION_PROP, null, name);
135    }

136 
137    /**
138     * Set the Location of this Activity.
139     * @param newLocation a non-null Point instance
140     * @throws IllegalArgumentException if the parameter is null
141     */

142    public void setLocation(Point newLocation) {
143       if (newLocation == null{
144           throw new IllegalArgumentException();
145       }

146       location.setLocation(newLocation);
147       firePropertyChange(LOCATION_PROP, null, location);
148    }

149    
150    /**
151     * Set the Size of this Activity.
152     * Will not modify the size if newSize is null.
153     * @param newSize a non-null Dimension instance or null
154     */

155    public void setSize(Dimension newSize) {
156       if (newSize != null{
157           size.setSize(newSize);
158           firePropertyChange(SIZE_PROP, null, size);
159       }

160    }

161 
162}

163 

在这个类中,我们定义两个List对象,分别对应该活动的输入转移和输出转移,因为对于一个完整的流程来说,每个活动都会转移的(或者有输入转移,或者有输出转移,或者两者都有),在这个类中,我们还注意到,每个改变对象属性的方法中,都会调用firePropertyChange方法,这个方面就是通知控制器,模型的属性发生发生变化了,让控制器根据相应的属性来刷新视图。
定义了活动的父类之后,我们就可以分别来定义开始活动,普通活动,结束活动对应的类了,具体代码如下:
开始活动:

 1package com.example.workflow.model;
 2 
 3public class StartActivity extends AbstractActivity{
 4    
 5    private staticfinallongserialVersionUID = 4639994300421360712L;
 6    private staticfinal String STARTACTIVITY_NAME = "START";
 7    
 8    public String getName() {       
 9       returnSTARTACTIVITY_NAME;
10    }

11 
12    public String toString() {
13       return"StartActivity " + hashCode();
14    }

15}

普通活动:

 1package com.example.workflow.model;
 2 
 3 
 4public class Activity extends AbstractActivity{
 5    
 6    private staticfinallongserialVersionUID = 3023802629976246906L
 7    private staticfinal String ACTIVITY_NAME = "ACTIVITY";
 8    
 9    public String getName() {       
10       returnACTIVITY_NAME;
11    }

12    public String toString() {
13       return"Activity " + hashCode();
14    }

15 
16}

17 

结束活动:
 1package com.example.workflow.model;
 2 
 3public class EndActivity extends AbstractActivity{   
 4    
 5    private static final long serialVersionUID = 316984190041034535L
 6    privates tatic final String ENDACTIVITY_NAME = "END";
 7    
 8    public String getName() {       
 9       returnENDACTIVITY_NAME;
10    }

11 
12    public String toString() {
13       return"EndActivity " + hashCode();
14    }

15 
16}

定义完这些活动之后,我们来定义流程模型,由于流程中包含多个活动,所以里面应该有个列表来维护这些对象,同样,流程中还包含多个转移,由于在活动模型中,已经维护了转移对象,所以这里就不维护这些转移对象了,具体代码如下:

 1package com.example.workflow.model;
 2 
 3import java.util.ArrayList;
 4import java.util.List;
 5 
 6/**
 7 * 流程模型,可以包含多个活动和转移模型
 8 * @author Administrator
 9 *
10 */

11public class WorkflowProcess extends ModelElement{       
12       
13       private static final long serialVersionUID = -5478693636480758659L;
14       /** Property ID to use when a child is added to this WorkflowProcess. */
15       public static final String CHILD_ADDED_PROP = "WorkflowProcess.ChildAdded";
16       /** Property ID to use when a child is removed from this WorkflowProcess. */
17       public static final String CHILD_REMOVED_PROP = "WorkflowProcess.ChildRemoved";     
18       private List activities = new ArrayList();
19       
20       /** 
21        * Add a Activity to this WorkflowProcess.
22        * @param s a non-null Activity instance
23        * @return true, if the Activity was added, false otherwise
24        */

25       public boolean addChild(Activity a) {
26              if (a != null && activities.add(a)) {
27                     firePropertyChange(CHILD_ADDED_PROP, null, a);
28                     return true;
29              }

30              return false;
31       }

32 
33       /** Return a List of Activities in this WorkflowProcess. The returned List should not be modified. */
34       public List getChildren() {
35              return activities;
36       }

37       
38       /**
39        * Remove a Activity from this WorkflowProcess.
40        * @param s a non-null Activity instance;
41        * @return true, if the Activity was removed, false otherwise
42        */

43       public boolean removeChild(Activity a) {
44              if (a != null && activities.remove(a)) {
45                     firePropertyChange(CHILD_REMOVED_PROP, null, a);
46                     return true;
47              }

48              return false;
49       }

50}

最后我们来定义转移模型,我们知道转移模型是链接两个活动的,所以在转移模型中,应该有个转移的源活动和目标活动,同时如果两个活动之间已经有转移连接时,就不能再在两者之间建立转移了,所以在两个活动之间建立转移时,必须先判断两者之间是否已经建立转移,所以转移模型具体代码如下:

 1package com.example.workflow.model;
 2 
 3/**
 4 *ATransitionbetweentwodistinctactivities.
 5 */

 6public class Transition extends ModelElement{
 7    
 8    private static final long serialVersionUID = 516473924757575767L;
 9    /**True,ifthetransitionisattachedtoitsendpoints.*/
10    private boolean isConnected;
11    /**Transition'ssourceendpoint.*/
12    private AbstractActivity source;
13    /**Transition'stargetendpoint.*/
14    private AbstractActivity target;
15    
16    /**
17     *CreateaTransitionbetweentwodistinctactivities.
18     *@paramsourceasourceendpointforthisTransition(nonnull)
19     *@paramtargetatargetendpointforthisTransition(nonnull)
20     *@throwsIllegalArgumentExceptionifanyoftheparametersarenullorsource==target
21     *@see#setLineStyle(int)
22     */

23    public Transition(AbstractActivity source, AbstractActivity target) {
24       reconnect(source, target);
25    }

26 
27    /**
28     *Disconnectthisconnectionfromtheactivitiesitisattachedto.
29     */

30    publicvoid disconnect() {
31       if (isConnected) {
32           source.removeTransition (this);
33           target.removeTransition (this);
34           isConnected = false;
35       }

36    }

37    
38    /**
39     *ReturnsthesourceendpointofthisTransition.
40     *@returnanon-nullActivityinstance
41     */

42    public AbstractActivity getSource() {
43       returnsource;
44    }

45 
46    /**
47     *ReturnsthetargetendpointofthisTransition.
48     *@returnanon-nullActivityinstance
49     */

50    public AbstractActivity getTarget() {
51       returntarget;
52    }

53 
54    /**
55     *ReconnectthisTransition.
56     *TheTransitionwillreconnectwiththeactivitiesitwaspreviouslyattachedto.
57     */
 
58    public void reconnect() {
59       if (!isConnected) {
60           source.addTransition (this);
61           target.addTransition (this);
62           isConnected = true;
63       }

64    }

65 
66    /**
67     *Reconnecttoadifferentsourceand/ortargetActivity.
68     *Theconnectionwilldisconnectfromitscurrentattachmentsandreconnectto
69     *thenewsourceandtarget.
70     *@paramnewSourceanewsourceendpointforthisTransition(nonnull)
71     *@paramnewTargetanewtargetendpointforthisTransition(nonnull)
72     *@throwsIllegalArgumentExceptionifanyoftheparamersarenullornewSource==newTarget
73     */

74    public void reconnect(AbstractActivity newSource, AbstractActivity newTarget) {
75       if (newSource == null || newTarget == null || newSource == newTarget) {
76           thrownew IllegalArgumentException();
77       }

78       disconnect();
79       this.source = newSource;
80       this.target = newTarget;
81       reconnect();
82    }

83}

到这儿,模型的定义已经全部完成,下一节我们将定义GEF框架中最重要的部分,也是最复杂的部分,控制器。

Feedback

# re: 流程设计器开发一(模型部分)  回复  更多评论   

2008-01-03 09:35 by yuan1129
写的非常好,希望能够提供源代码下载,谢谢。

# re: 流程设计器开发一(模型部分)  回复  更多评论   

2008-01-03 09:51 by 玩转Java
在流程设计器三中可以下载代码。

# re: 流程设计器开发一(模型部分)  回复  更多评论   

2008-07-29 17:08 by zhang li guo
知道你是谁了。 :)

还记得你刚进公司的第一天谈话。那些天,做引擎的单元测试,感觉做的一般。
后来就让你全心做设计器了。 :)

三年很快过完了。

说句实话,我非常怀念那段开发产品的日子,充满激情和梦想……
也很有幸,后来我们临别前在投标项目中又合作一把。

最近我业余时间,在和一个老外合作一个eclipse插件,有空来看看
http://code.google.com/p/camel-route-viewer/

离你的eclipse水平差远了。

:)

# re: 流程设计器开发一(模型部分)  回复  更多评论   

2008-07-29 17:09 by zhang li guo
MSN:catflame AT msn.com
加我

# re: 流程设计器开发一(模型部分)  回复  更多评论   

2010-03-22 21:24 by 杨志斌
很强大

只有注册用户登录后才能发表评论。


网站导航: