EMF介绍系列(三、定制应用程序的基本方法)

借助EMF的帮助,不用亲自编写一行代码就可以生成一个完整的应用程序,你是不是对EMF有些感兴趣了?不过生成的应用程序看起来都像是从同一个模子里出来的,即 一个多页编辑器,一个大纲视图和属性页,这当然无法满足所有人的需求。不用耽心,只要了解EMF的机制,按照我们的要求修改这个应用程序并不是一件很困难的 事情。

首先大概的看一下EMF为我们生成了哪些东西吧。按照前文的操作,EMF应该一共生成了四个插件项目:com.my.shop、 com.my.shop.edit、com.my.shop.editor和com.my.shop.tests,其中最后一个项目是方便我们编写单元测 试的框架代码,这里我们先不管它,暂时把注意力集中在前三个项目上。

第一个项目是模型部分,主要包含你定义的ecore模型里各类型(EClass,在ecore元模型里类型称为EClass,属性称为 EAttribute)对应的java接口和缺省的实现类代码,例如Product.java和ProductImpl.java,它们分别被放置在 com.my.shop和com.my.shop.impl包里;一个工厂类(ShopFactory)使用工厂模式创建新的模型实例;一个 Package类(ShopPackage)维护关于元模型的信息,提供了一堆静态成员变量;此外还生成了ShopAdapterFactory和 ShopSwitch这两个类,它们是可选的,它们俩的作用这里卖个关子暂时先不说。

第二个项目是.edit部分,这里面包含了一系列ItemProvider类,用来为在jface的各种查看器(Viewer)里显示这些模型对象 提供便利,以CategoryItemProvider为例,它实现了IStructuredItemContentProvider、 ITreeItemContentProvider和IItemLabelProvider这些接口,注意把这些接口名字中的"Item"去掉就是 jface里需要的Provider,可以把这些带有"Item"字样的Provider想象成对jface相应Provider的包装。有了这些 Provider,在应用程序里使用jface的TreeViewer、TableViewer等查看器就很方便了。(前面讲GEF的一个帖子里曾利用 EMF构造模型,当时使用的就仅仅是模型部分,因为并未用到jface查看器。所以视你的应用程序而定,可以只生成模型部分来用,.edit部分依赖模型部分,而反之不然。)

第三个项目是编辑器,这个部分依赖.edit部分,主要包含了几个UI方面的类。EMF为我们生成的这个编辑器有两个用途:一是让我们可以不用从零开始,而是在这个编辑器的基础上进行修改得到自己的编辑器; 二是通过这些代码展示怎样在应用程序里使用前两个项目里的那些类,编辑器包含那么多Tab正是为了演示各种查看器的用法。下面来说一下怎样定制生成的应用 程序。

一、修改ecore模型和genmodel模型

在ecore模型和genmodel模型里我们可以通过修改一些属性改变所生成的代码,例如希望新创建的类别和产品的名称不是空字符串而是" (Unnamed)",就可以在类图里修改NamedElement类的name属性的"Default Value Literal"属性(没错,属性的属性。见图1)。修改ecore模型后,必须更新genmodel模型,方法是在Package Explorer里右键单击shop.genmodel文件,在弹出菜单里选择"Reload...",这样genmodel会从修改后的ecore模型 里获得修改过的信息。之后,再次从genmodel模型生成一遍代码,这样得到的程序运行后,类别和产品的名称缺省就是"(Unnamed)"了。在 genmodel模型里则可以定制更多属性,例如所生成的每个项目的id、生成类所在的包名(本例中为com.my)、类名前缀(本例中为Shop)、是否生成图标、标准属性页里各属性的分类等等。

file
图1 在EclipseUML类图里修改属性的缺省值

有人对 ecore模型和genmodel模型各自的用途搞不清楚,其实因为JET是直接通过genmodel生成代码的,所以像是否生成图标这些信息放在 genmodel里是比较合适的,而ecore模型里定义的是各个类型以及之间的关系,所以像属性缺省值这样的信息是应该放在ecore里没错的。

二、直接修改生成的代码

前面的方式是让EMF生成我们想要的程序,好处是非常直观和方便,缺点是只有有限的信息可被定制,而一个复杂的应用程序是不太可能仅仅通过填写一些 属性就被生成出来的,所以我们需要代码级的定制。有一个事实你需要知道:无论对ecore还是genmodel模型定制,产生的结果最终都要反映到生成的 代码。让我们对比一下修改名称缺省值前后的代码(因为类图里NamedElement被定义为抽象类,所以这段代码的位置在 CategoryImpl.java和ProductImpl.java里),这是之前生成的代码:

/**
 * The default value of the '{@link #getName() <em>Name</em>}' attribute.
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @see #getName()
 * @generated
 * @ordered
 */
protected static final String NAME_EDEFAULT = null;

下面是定制之后的代码:

/**
 * The default value of the '{@link #getName() <em>Name</em>}' attribute.
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @see #getName()
 * @generated
 * @ordered
 */
protected static final String NAME_EDEFAULT = "(Unnamed)";

可以看到只是NAME_EDEFAULT的值发生了变化。也就是说,如果我们不修改ecore模型,而是手工修改这段代码也能达到同样的目的。对于熟悉 EMF的开发人员来讲,修改代码很多时候甚至比修改ecore/genmodel模型更加快速(因为省去了reload genmodel和生成代码这两步),但一定要记住,在修改代码的同时修改这部分代码前的javadoc。因为在每次重新生成代码的时候,JET通过原有 代码前的javadoc判断是否覆盖这段代码,如果javadoc里包含一行"@generated"则覆盖,否则保持这部分代码不变。为了让我们手工的 修改不在以后的生成中消失(这可是很大的损失),可以直接删除@generated这一行,更好的办法是将其改为一个统一的字符串,例如 "@generated NOT",以便将来通过javadoc搜索找到自己都在哪些地方做了手工修改。

下面再通过一个例子演示怎样修改EMF生成的代码。假设现在有个需求是让所有价格大于等于100元的产品自动打95折,而小于5元的商品一律按5元算,那么我们需要修改ProductImpl的getPrice()方法,修改前的代码如下:

/**
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated
 */
public double getPrice() {
    return price;
}

这是修改后的代码,再次提醒一定别忘记修改@generated那行注释:

/**
 * <!-- begin-user-doc -->
 * 所有价格大于等于100元的产品自动打95折,而小于5元的商品一律按5元算。
 * <!-- end-user-doc -->
 * @generated NOT
 */
public double getPrice() {
    if (price >= 100)
        return price * 0.95;
    if (price < 5)
        return 5d;
    return price;
}

至于使用哪种方式进行定制,我个人建议能通过修改ecore/genmodel模型解决的问题就不要直接修改代码,尽量减少手工修改的地方有利于保持思路清晰,其实在实际使用中大部分定制还是需要修改代码才能实现的。

代码下载

点此下载修改后的项目

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/11/19/280499.html

EMF介绍系列(二、从模型生成应用程序)

还是从一个例子里看看EMF的使用方法和作用吧。假设我们的应用是一个网上商店,在系统里有这些对象:商店Shop、类别Category、 商品Product,其中类别可以包含子类别。现在我们用EMF从头到尾生成一个可以管理类别和商品的应用程序。如果你的Eclipse里还没有安装 EMF,在eclipse.org/emf 下载适合你Eclipse版本的EMF SDK,建议你下载全部包含的那种。安装后,在新建对话框里会增加EMF的类别,如图1所示:

file
图1 新建向导里的EMF类别

其中“EMF Model”是从已有的模型文件创建genmodel模型,这个模型是专门用来生成代码的;如果你手里已经有一个模型(比如一个.mdl文件或是一个.xsd文件等等),可以选择新建“EMF Project”,这样在向导的后面部分里会要求提供已有的模型文件;因为我们打算自己从头开始建立这样的模型,所以选择“Empty EMF Project”,和建立普通插件一样,要提供一个插件名称,我们为这个商店项目起名为com.my.shop,然后按Finish即完成向导。目前这个新建立的 项目里还没有包含任何代码,只是在META-INF/MANIFEST.MF文件里定义了对EMF相关插件的依赖。

下面开始定义ecore模型,我比较喜欢图形化的方式,因为看起来很直观,所以我使用Omondo公司的EclipseUML插件来画类图,这个插件的免费版本在 它们网站下载,注意下载适合你的Eclipse的版本。当然rose也不错而且更加稳定,但它 不是免费的,而且只能在Windows里使用。和Rose不同,EclipseUML对EMF有特别的支 持,安装这个插件后,我就们可以在项目里新建一个“EMF Class Diagram”,这样会同时创建一个.ecd文件和一个.ecore文件,EclipseUML编辑的类图信息会保存在这两个文件里,前者主要是图形方 面的内容,而后者是真正的模型信息,注意这两个文件中的任何一个都不要搞丢了,最好能经常备份一下。

在EclipseUML里编辑类图很简单,需要注意的是,两个对象之间如果有关联,要仔细考虑关联是否为“包含”关系(一般在UML中以黑色菱形表 示),如果一个类没有包含在任何其它类里,则这个类的实例不会被保存到文件。例如图2中Category包含在Shop中,Product包含在 Category中。这样,所有的对象都直接或间接的被Shop对象包含。换句话说,如果以Shop作为“根”,所有的对象都可以被保存到文件里。

EMF对java基本类型和一些常用类做了包装,例如int->EInt,java.lang.Integer->EInteger以 及java.util.List->EList等等,所以在定义类的属性时要使用这些EMF的类型,当然也有办法使用自定义类型(以后会用到)。我 们例子里的模型相当简单,一共只有三种业务对象(为了更加直观,我们增加了一个NamedElement接口),现在网上 商店类图的第一个版本如图2所示。

file
图2 网上商店类图(版本1.0)

接下来就要生成代码了。EMF使用JET利用模板生成代码(前面曾介绍过JET),所以要把ecore模型转换为可以被JET利用的genmodel模型,具体的操作是按 ctrl+n新建一个EMF Model,在这个向导的第一步指定名称shop.genmodel,第二步选择从ecore模型导入,第三步选择ecore模型文件 (shop.ecore),这样就建立了缺省的genmodel模型,在这个模型的基础上还可以做一些定制工作,例如每个属性的描述信息等等。

有了genmodel模型,离得到可用的java代码就只有一步之遥了。打开shop.genmodel文件,在根节点上点开右键菜单(见图3),如果只想生 成模型代码选择“Generate Model Code”,如果需要.edit的代码(EMF提供的一些ItemProvider和AdapterFactory,帮助实现编辑器)和可用的编辑器,选 择“Generate All”最方便,这也是例子里选择的方式。EMF的代码生成器为模型、.edit、编辑器和测试代码各生成一个插件项目 (com.my.shop、com.my.shop.edit、com.my.shop.editor和com.my.shop.tests),前三个是后者依赖前者的关系。这里插上一句,即使没有 用EMF的项目,也建议把模型和界面使用不同的插件项目分开,这样做有很多好处,主要是灵活性大大提高了。

file
图3 从菜单里选择生成部分或全部代码

现在可以运行起来看看效果了,注意我们甚至连一句代码也没有写呢。EMF为我们生成了一个新建向导(New Wizard),利用这个向导可以生成新的Shop实例,注意在第三步要选择以Shop类为根类型。编辑器的运行界面如图4所示,它的外观虽然有待改进,但功能已 经足够我们对网上商店里的类别和产品进行编辑了。在以后的帖子里,我们要对网上商店的ecore模型和编辑器的界面做一些修改。

file
图4 缺省的编辑器界面

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/11/13/275054.html

EMF介绍系列(一、EMF与MDA)

接触Eclipse一段时间的朋友应该都听说过EMF这个名字,EMF是Eclipse Modeling Framework的缩写,它是Eclipse的一个重要的子项目,如果翻译成中文就是“Eclipse建模框架”。其实只从这个名字还真是难以确定它的 作用是什么,我认为要完全掌握EMF应该对模型驱动开发(MDA)有一定的了解,而EMF可以看作是Eclipse上的MDA一个实现(代码生成是MDA 的重要组成部分之一),它能够生成在Eclipse上执行的代码。可惜我对MDA没有系统研究过,对EMF的应用大多是为了减少模型修改带来的影响,所以 也希望EMF能带我进入MDA的世界。

MDA讲究的是把模型和应用系统实现分开,模型是最重要的部分,可以说有了清楚的模型,就完成了一半的工作。模型是由元模型(Meta Model)定义的,例如UML里“类”和“属性”这些概念是在UML的元模型里定义的,而元模型又是由“元元模型”来定义,后者多是自描述的,也就是能 够自己定义自己,所以很少见到“元元元模型”的概念。在MOF规范里, 元元模型处于M3层,元模型处于M2层,往下的M1层是模型,而M0层是实例。EMF定义了一套Ecore元模型,该模型是EMOF(MOF的一个子集, MOF是Meta-Object Facility的缩写)的一个实现,这是一个自描述的模型,可以认为它处于MOF中的M2层,即与UML元模型相同的位置。用Ecore元模型可以定义 ecore模型,也就是.ecore文件,这个模型处于M1层,而ecore模型的实例处于M0层。关于MOF的更多概念请参考MOF规范和相关文档, MDA的各种概念是相当多的,研究它的人也很多,我认为EMF算是比较务实的一派。如果以后有机会深入研究Ecore元模型,我也会把心得写在这里供大家 参考。

EMF自发布以来一直受到Eclipse社区的热情拥护,目前很多Eclipse的子项目都是基于它开发的,可见EMF确实能给开发者带来好处。随着EMF的成长,出现了越来越多的文档,在eclipse.org/emf上就可以找到不少,最全面和权威的当属这本Eclipse Modeling Framework A Developers Guide, 完整的讲解了EMF,虽然针对的版本较早,但绝大部分内容还是适用的;作为入门读物,网站上Documents里列出的一些教程也是不错的选择;EMF的 新闻组更是一个很好的交流场所,Ed Merks(EMF设计师之一)和其它几位开发人员可以说是有问必答,感谢他们的认真态度。

通过在一些项目里使用EMF,我也逐渐感觉了到它起到的作用,特别是当模型里各种元素和关系比较多时,EMF的代码生成功能会节省不少工作量,对于 我们开发人员来讲,这不就是最大的好处吗。其实用EMF构造一个应用的步骤很简单:1、构造模型,2、生成代码,前者可以通过UML类图、Java接口、 XML Schema等多种方式定义,后者可以选择只生成模型部分的代码,也可以同时生成编辑器部分的代码,对这个编辑器做一些定制就可以得到符合需求的应用程 序。

类图可以帮助我们直观的了解应用系统里各对象的关系,但在开发过程中,类图里的定义很可能被修改,如果这一修改没有及时反映回类图,类图就会逐渐变 得不准确而失去作用。但是保持代码和类图的一致是一件很烦琐的工作,有时由于项目管理的需要,类图又必须保证能够反映系统的真实结构。比较好的解决方法是 让代码由类图直接生成,模型需要修改时也在类图上做改动,并且重新生成代码,这正是EMF的专长。

说了这么多,你可能还是没弄明白EMF到底能为我们带来哪些好处,是怎样为我们节省工作量的,从下个帖子开始我们将一步步了解怎样使用EMF构造应用程序。因为是边用边写,所以这个系列的帖子都不会太长,相信后面部分会以心得和技巧等内容为主。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/11/12/275024.html

Debian安装配置常用软件

1、安装j2sdk,下载j2sdk-1_4_2_09-linux-i586-rpm.bin文件,chmod 755 j2sdk-1_4_2_09-linux-i586-rpm.bin,运行后得到.rpm文件,用alien转换为.deb包,然后dpkg -i  xxx.deb即可,安装后的文件在/usr/java目录;

Update: 在debian里用上面rpm包安装jdk有问题,一些jar文件没有被展开,所以下载非rpm的那个包比较好,chmod +x后解压到想要的目录即可。在~/bash_profile里设置JAVA_HOME和PATH=$PATH:$JAVA_HOME/bin, gnome的shell要在菜单里设置为登录shell。

2、安装eclipse,下载后直接tar zxvf即可,然后在桌面建立一个快捷方式,vm参数指向刚才安装的jdk下的bin/java;特别要注意一个问题,在windows下开发的插件项目 拿到linux下,META-INF目录和里面的MANIFEST.MF是小写的,这样会造成编译时出现很多错误,所以一定要保证它们是全部小写的。

file

图1 运行在Linux下的Tree

3、字体和美化问题真是有够麻烦。字体的美化(毕竟眼睛健康很重要),暂时使用这个方案,比较省事吧;解决xmms乱码问题,最简单的方法,卸掉xmms装beep-media-player。

4、设置gnome里的环境变量,//todo,修改.bash_profile后在gnome里竟然不起作用,但在console里就有用;

5、查看deb包安装后的位置

dpkg -L xxx

6、屏幕截图用printscreen即可,可以截全屏或一个窗口区域;或者scrot -d 5延迟5秒截图,适合截菜单界面,因为gnome里打开菜单后热键会失效;

7、安装gnome主题(gnome-looks.org),要在~/.themes和~/.icons下分别放不同的主题文件;模拟mac的 start bar要装gdesklets,我感觉不太实用;安装gdm主题,运行gdmsetup;安装xmms主题,似乎要重启gnome才可生效;

8、gnome下的音量控制用gnome-media;gnome下的apt用gnome-apt(alpha版,小心使用);安装deborphan可以找到不被任何包使用的库,然后apt-get remove之就可以节省出一些空间;

9、其他很多软件,推荐:Windows软件的Liunx版本替换表,俄国人写的;

10、解压缩.tar.gz文件的方法:tar zxvf a.tar.gz;解压缩.tar.bz2文件

tar xfj a.tar.bz2

11、关于网卡的配置:apt-get install etherconf,然后dpkg-reconfigure etherconf;如果无线网络需要密码,iwconfig eth1 key xxxxxx就可以了 ,或者在/etc/network/interfaces里加上wireless_key xxxxxx的配置;要改为自动获得ip时,执行dhclient eth1;

12、从www.rssowl.org下载rssowl阅读blog,但我发现rssowl在linux下内置的浏览器有些问题,很多格式丢失了,看起来会比较费力;

13、要在linux里远程桌面到windows xp系统

apt-get install grdesktop

14、在gaim里用代理服务器上msn,直接在首选项里设置http代理会提示“访问被禁止:代理服务器禁止端口1863流过”,应该在msn帐号里选择使用http方式,并设置代理服务器;

15、安装mysql很简单,直接apt-get install mysql-server-4.1就可以了,使用上比windows的gui方式稍微麻烦些,见这篇文章;另外,从windows版本的mysql里直接拷出来的库不能拿来用,因为表名和字段名在linux里区分大小写。

16、安装teTex:在sources.list里添加ustc的源deb http://debian.ustc.edu.cn/debian-uo/ sid marillat rareware misc ustc,然后

apt-get install tetex-bin tetex-base tetex-extra cjk-latex
apt-get install dvipdfmx dvipdfm-cjk ttf-arphic* tfm-arphic*

为了能使用中文,在这个网站下载gbkfonts工具,静态的那个即可。用下面的命令配置一下:

mkdir ~/texmf
cd ~/texmf
gbkfonts /usr/share/fonts/zh/simsun.ttf song 
gbkfonts /usr/share/fonts/zh/simkai.ttf kai
mv cid-x.map dvipdfm/config/
mkdir dvips/config
mv cjk.map dvips/config/

cp /usr/share/texmf-tetex/dvips/config/config.ps dvips/config/
echo "p +cjk.map" >> dvips/config/config.ps
mv pdftex.cfg pdftex/config/
mktexlsr

配置中文的部分来自这个网页,修改了一些地方。在.tex文件里像下面这样写就可以显示中文了

\documentclass{article}
\usepackage{CJK}
\begin{document}
\begin{CJK}{GBK}{song}
这是 latex
\end{CJK}
\end{document}

17、apt安装samba,然后修改/etc/samba/smb.conf,把 security = user前的分号去掉; touch /etc/samba/smbpassword 生成smbpassword文件,再smbpassword -a username添加一个user。显示中文,在smb.conf里边的[global] 栏目下添加一行:client code page 936(用windows访问正常,但用palm上的wifile访问时文件名的中文部分都是空白)

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/09/30/247027.html

[翻译]Eclipse里的IAdaptable是什么?

原文地址:http://www.eclipsezone.com/articles/what-is-iadaptable/

IAdaptable在Eclipse里是一个非常重要的接口。对于Eclipse开发老手来说,它就像异常处理和抽象类一样寻常;但是对新手而言,它却令人感到困惑和畏惧。这篇文章将向你解释IAdaptable到底是什么,以及它在Eclipse里起到的作用。

类型转换

Java是所谓的强类型语言,也就是说,每个实例都对应一个类型。其实类型分为两种:声明类型和运行时类型(也分别被称为静态类型和动态类型)。像Python这样的弱类型语言常被称为无类型的语言,其实严格说来不是这样,因为每个实例都对应一个运行时类型,只是你并不需要声明这一点而已。

现在回到Java,为了能够执行一个类的某个方法,这个方法必须在声明类型中可见,换句话说,即使在运行时实例是某个子类型,你也只能执行那些父类型里定义的方法。

List list = new ArrayList();
list.add("data");       // 正确,add是List里定义的方法
list.ensureCapacity(4); // 不正确,ensureCapacity()只在ArrayList被定义

如果一定要执行特定类型的方法,我们必须先强制转换这个实例到正确的类型。对于上面的例子,我们可以将list转换为ArrayList(译注:原文In this case, we can cast ArrayList to List,怀疑是笔误),因为ArrayList实现了List接口,你甚至可以在运行时通过instanceof关键字检验list是否为ArrayList的一个实例。

可扩展的接口

不幸的是,一个类可能并没有实现你需要的接口,这样就无法进行强制类型转换了。原因有很多,比如只在少数情况下才需要这个接口,或者你需要的接口是在另一个不相关的库里,又或者接口是有了类以后才开发出来的,等等。

这时你就需要IAdaptable了。可以把IAdaptable想象为一个能够动态进行类型转换的途径。对比下面的直接类型转换:

Object o = new ArrayList();
List list = (List)o;

换一种方式,我们可以这样做:

IAdaptable adaptable = new ArrayList();//译注:这里的ArrayList应该不是指java.util.ArrayList
List list = (List)adaptable.getAdapter(java.util.List.class);

这就是上面所说的动态类型转换,我们所做的事情是试图把adaptable转换为一个List实例。

那么,当可以直接转换的时候为什么要费这个力气通过getAdapter()来转换呢?其实这种机制可以让我们将目标类转换为它并没有实现的接口。举个例子,我们可能想把一个HashMap当作List来用,尽管这两个类的性质并不相同,可以这么做:

IAdaptable adaptable = new HashMap();//译注:这里的HashMap应该不是指java.util.HashMap
List list = (List)adaptable.getAdapter(java.util.List.class);

实现IAdaptable接口

大部分IAdaptable的实现是一些if语句的叠加,比如我们现在要实现HashMap的getAdapter()方法,它看起来可能是这样:

public class HashMap implements IAdaptable {
  public Object getAdapter(Class clazz) {
    if (clazz == java.util.List.class) {
      List list = new ArrayList(this.size());
      list.addAll(this.values());
      return list;
    }
    return null;
  }
  ...
}

所做的就是返回一个适配器(adapter,更确切的说是一个副本),而不是进行直接的类型转换。如果参数类型没有被支持,惯例是返回null值(而非抛出异常),代表这个方法失败了。因此,在调用这个方法时,不应该假定它总是返回非null值。

PlatformObject

当然,如果你希望增加一个新的被支持的adapter类型时必须编辑这个类才行(译注:在getAdapter()里增加更多的if语句),这会比较辛苦。而且,既然你已经知道了这个类型,何不直接修改接口声明呢?其实有很多原因使得你并不希望直接编辑这个类(例如更容易保持向下兼容性),也不想改变它的类型(HashMap虽然不是一个List,但可以转换过去)。

Eclipse通过PlatformObject抽象类来解决以上问题,它为你实现了IAdaptable接口,Eclipse平台(Platform)提供了IAdapterManager的一个实现,并且可以通过Platform.getAdapterManager()访问到,它把所有对getAdapter()的请求(调用)委托给一个名为IAdapterManager的东西。你可以将它想象为一个巨大的保存着类和adapter信息的Map,而PlatformObject的getAdapter()方法会查找这个Map。

适配已存在的类

这样,PlatformObject不需要重新编译就能够支持新的adapter类型,这一点在Eclipse里被大量使用以支持workspace的扩展点。

现在假设我们想要将一个只包含String类型元素的List转换为一个XMl节点,这个节点的格式如下:

<List>
  <Entry>First String</Entry>
  <Entry>Second String</Entry>
  <Entry>Third String</Entry>
</List>

因为toString()方法可能有其他用途,我们不能通过覆盖toString()方法来实现这个功能。所以,我们要给List关联一个工厂类以处理XML节点类型的适配请求。要管理工厂类需要以下三个步骤:

1、由List生成一个Node,我们把这个转换过程用IAdapterFactory包装起来:

import nu.xom.*;
public class NodeListFactory implements IAdapterFactory {
  /* 可以转换到的类型 */
  private static final Class[] types = {
    Node.class,
  };
  public Class[] getAdapterList() {
    return types;
  }
  /* 转换到Node的功能代码 */
  public Object getAdapter(Object list, Class clazz) {
    if (clazz == Node.class && list instanceof List) {
      Element root = new Element("List");
      Iterator it = list.iterator();
      while(it.hasNext()) {
        Element item = new Element("Entry");
        item.appendChild(it.next().toString());
        root.appendChild(item);
      }
      return root;
    } else {
      return null;
    }
  }
}

2、把这个工厂类注册到Platform的AdapterManager,这样当我们希望从List的实例中获得一个Node实例时,就会找到我们的工厂类。注册一个工厂类的方式也很简单:

Platform.getAdapterManager().registerAdapters(
  new NodeListFactory(), List.class
);

这条语句将NodeListFactory关联到List类型。当从List里请求adapter时,Platform的AdapterManager会找到NodeListFactory,因为在后者的getAdapterList()方法的返回结果里包含了Node类,所以它知道从List实例得到一个Node实例是可行的。在Eclipse里,这个注册步骤一般是在plugin启动时完成的,但也可以通过org.eclipse.core.runtime.adapters扩展点来完成。

3、从List获得Node,下面是例子代码:

Node getNodeFrom(IAdaptable list) {
  Object adaptable = list.getAdapter(Node.class);
  if (adaptable != null) {
    Node node = (Node)adaptable;
    return node;
  }
  return null;
}

总结

综上所述,要在运行时为一个已有的类增加功能,所要做的只是定义一个用来转换的工厂类,然后把它注册到Platform的AdapterManager即可。这种方式在保持UI组件和非UI组件的分离方面特别有用。例如在org.rcpapps.rcpnews.ui和org.rcpapps.rcpnews这两个plugin里,前者的IPropertySource需要与后者的数据对象(data object)相关联,当前者初始化时,它将IPropertySource注册到Platform,当数据对象在导航器(navigator)里被选中的时候,属性视图里就会显示正确的属性。

显然,java.util.List并不是PlatformObject的子类,所以如果你希望能够编译这里所说的例子,必须建立一个List的子类型。注意,可以直接实现IAdaptable接口,而非必须继承PlatformObject抽象类。

public class AdaptableList implements IAdaptable, List {
  public Object getAdapter(Class adapter) {
     return Platform.getAdapterManager().getAdapter(this, adapter);
  }
  private List delegate = new ArrayList();
  public int size() {
    return delegate.size();
  }
  ...
}

最后,例子里生成XML的部分使用了XOM的类库。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/09/24/243312.html

关于本体编程的实现

临近期末,我有一门课程的期末项目是做一个教育领域的本体应用系统,所以最近经常思考本体在这样一个系统中所起的作用,以及该如何实现。(本体是否只能在web环境下发挥作用,使用本体描述一个独立系统的模型是否值得?)

假设要做的是选课系统,很容易看出系统里应该有这些对象:课程、学生、教师,它们之间互有联系。现在的问题是,本体、Java类和数据库各扮演怎样的角色?我目前想到的方法有以下几个:

  1. 在本体(owl)里建立这些类和关系,在Java里建立同样的Bean类,运行时系统把本体里的individuals转换为Java类的实例,也就是在内存里得到一个Java实例的集合,选课的各种操作就是对这个集合的修改,退出系统时再进行反向转换,把修改反映到本体里。在这样的实现方法中,本体实际上扮演了数据库的角色,所以当数据(individuals)很多的时候,效率会很成问题。(Protege的owl编辑工具提供了从本体生成EMF模型代码的功能,虽然目前还是alpha版,这也许将成为同步两种模型的不错选择。)

  2. 对上面的方法进行修改,让本体不保存individuals,对Java实例集合的修改最终反映到关系数据库里(利用hibernate不会很困难),运行时系统直接从数据库里取得数据转换为Java实例。这样的实现可以解决效率问题,但和传统应用区别不大,本体的作用几乎为零。

  3. 另一种比较极端的做法是只用本体维护类和individuals,Java方面没有任何与应用有关的模型,系统一开始把本体读入内存以rdf图的方式存在,选课操作转换为对此图的修改(利用Jena等API)。这个方法存在两个问题,一是效率,二是Java代码的可读性下降,这就相当于直接使用JDBC而不是hibernate对数据库操作的区别。

在solo项目里我使用的是第一种方式,各方面效果还可以接受,但本体的作用发挥得很不够。这是很重要的方面,因为如果和传统项目没有区别,辛辛苦苦引入本体又是为了什么,特别是对本体推理的功能,我想最关键的问题还是要找出最适合应用的本体,定义本体的确是一门学问。

Update:IBM Alphaworks也提供了一组本体工具(包括Orient、EODM和RStar),对EMF的支持应该不错。今天初步试了一下Orient,它只支持RDF(S),而不支持OWL,所以无法满足课程项目的要求,Orient的目前版本还有一些小bug,除已知的那些以外,我把.ontology文件输出为ecore模型总是不成功,而输出为rdf是可以的。

Update:推荐一个关于本体和模型驱动的幻灯片,主要内容是介绍应该如何利用UML的可视化编辑功能和元模型的扩展功能来构造本体,这里面介绍了相当多的相关概念(其中很多我甚至没听说过),以及它们出现的原因,比较有利于我们理清思路。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/06/05/168240.html

巧遇老同事

今天中午吃完饭在公司楼下闲聊的时候,有个人走到我面前说:“还记得我吗?”。我记性很差的,认人的能力也不行,不过一看到他我马上说出了他的名字(CQD),真没想到在这里能遇到他。说来也奇怪,上周五在回家的路上我还忽然想起他,不知道他最近混得怎么样。

我们俩原来在同一家公司干过几个月,当时在同一个项目小组里搞开发,后来由于各自的原因先后离开,时间一久就断了联系,到现在有两三年没见面了。其实我对他了解的也不多,但在我看来他是个很淳朴的人,不仅外表看起来忠厚老实,讲起话来也不急不躁,很平和,完全没有“社会上”的感觉,今天和他聊了一会儿竟然觉得心里一丝安慰。

他现在发展得很不错,在这里工作两年,工资已经是那时的好几倍了,他说:“这已经是我第五个工作了!”,看得出他对目前的状况很满意,他还说不打算再跳了。我也挺替他感到高兴的,不过想想自己,同样的年龄,却还处于艰苦的学习和积累经验阶段,即将面对的社会充满竞争和变数,一年后我能追上他吗,两年?

最后我们互留了MSN,我看到他的昵称是"Working is beautiful"。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/05/30/165204.html

世界地球日

今天是第36个世界地球日,也是我26岁生日,今年的生日有个惊喜。

file

早上刷牙时发现,有只大白猫在窗台上生下两只小猫,小猫身体小小的眼睛紧闭,简直就像两只小白老鼠。九点钟左右母猫离开不知去哪了,等了两个小时还没回来,可能是发现我在看她觉得不安全吧,所以我决定自己先给小猫喂点奶。谁知小猫用的东西很难买,连跑了三个地方,才在宠物医院买到猫牛奶(要医生开才行)和专用奶瓶。等我兑好温牛奶,都下午两点钟了,谁知小猫虽然五个小时没吃东西但就是不吃我拿的奶,把它拿在手里它就拼命挣扎和叫唤,试了一个多钟头还是没用,还是把它们放回原处让它们睡觉吧。

过了一会儿听到窗台上有动静,悄悄过去一看,原来是那只母猫回来了,她先是对两只小猫叫了几声,然后一次一个把小猫叼在嘴里带走了。

file

虽然一天没能去上班,但小猫终于和妈妈团聚了,我也放心了,祝你们一生平安。

Update04/23: 没想到今天(也许是昨天晚上)母猫带着小猫们又回来了,一共有四只小猫呢,它们好象把窗台当成自己的窝了。GF盛了一些牛奶和猫粮放在窗台上,刚才去看母猫已经吃掉了:)

file

Update:更多照片见http://whitecats.mblogger.cn/

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/04/22/143469.html

由于Eclipse版本不符造成的异常

前几天把GEF版本从3.0.1升级到了3.1M6,发现以前运行正常的GEF程序现在总出现下面这个异常,例如在我移动一个节点时,或者创建一个新节点时。

!ENTRY org.eclipse.ui 4 0 2005-04-19 23:12:55.974
!MESSAGE tried to access method org.eclipse.ui.views.properties.PropertySheetEntry.refreshFromRoot()V from class org.eclipse.gef.ui.properties.UndoablePropertySheetEntry
!STACK 0
java.lang.IllegalAccessError: tried to access method org.eclipse.ui.views.properties.PropertySheetEntry.refreshFromRoot()V from class org.eclipse.gef.ui.properties.UndoablePropertySheetEntry
    at org.eclipse.gef.ui.properties.UndoablePropertySheetEntry.access$0(UndoablePropertySheetEntry.java:1)
    at org.eclipse.gef.ui.properties.UndoablePropertySheetEntry$1.commandStackChanged(UndoablePropertySheetEntry.java:103)
    at org.eclipse.gef.commands.CommandStack.notifyListeners(CommandStack.java:253)
    at org.eclipse.gef.commands.CommandStack.execute(CommandStack.java:141)
    at org.eclipse.gef.tools.AbstractTool.executeCommand(AbstractTool.java:374)
    at org.eclipse.gef.tools.AbstractTool.executeCurrentCommand(AbstractTool.java:386)
    at org.eclipse.gef.tools.DragEditPartsTracker.performDrag(DragEditPartsTracker.java:450)
    at org.eclipse.gef.tools.DragEditPartsTracker.handleButtonUp(DragEditPartsTracker.java:320)
    at org.eclipse.gef.tools.AbstractTool.mouseUp(AbstractTool.java:1035)
    at org.eclipse.gef.tools.SelectionTool.mouseUp(SelectionTool.java:545)
    at org.eclipse.gef.EditDomain.mouseUp(EditDomain.java:259)
    at org.eclipse.gef.ui.parts.DomainEventDispatcher.dispatchMouseReleased(DomainEventDispatcher.java:374)
    at org.eclipse.draw2d.LightweightSystem$EventHandler.mouseUp(LightweightSystem.java:548)
    at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:136)
    at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:82)
    at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:842)
    at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:2908)
    at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:2541)
    at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:1612)
    at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:1578)
    at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:293)
    at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:144)
    at org.eclipse.ui.internal.ide.IDEApplication.run(IDEApplication.java:102)
    at org.eclipse.core.internal.runtime.PlatformActivator$1.run(PlatformActivator.java:228)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:333)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:150)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.eclipse.core.launcher.Main.invokeFramework(Main.java:268)
    at org.eclipse.core.launcher.Main.basicRun(Main.java:260)
    at org.eclipse.core.launcher.Main.run(Main.java:887)
    at org.eclipse.core.launcher.Main.main(Main.java:871)

调试了很久也没找到原因,最后在GEF新闻组里得到了答案,原来GEF3.1M6要求Eclipse的版本在3.1M6或以上,而我正在使用的是Eclipse 3.1M5a,换到3.1M6一试果然OK。

如果你遇到类似的奇怪异常,不妨也先检查一下Eclipse和插件的版本。

利用Digester把XML转换为Java对象

在一个比较现实的应用系统里,经常需要有一些配置文件。简单的属性使用.properties文件即可,但要配置一些复杂对象,则应该考虑使用xml文件。一般用来读取xml文件的工具包有DOM、SAX和JDOM等,但用过的人都知道,它们属于比较底层的API,写起来代码量很大,而且如果修改了xml文件的格式,代码也要做大幅度的改动。Jakarta Commons项目里的Digester包,可以轻松实现xml文件到Java对象的转换,请看下面这个例子。

file

在一个项目里,需要提供一些统计图,但图的内容暂时未能确定。所以我决定让图可以配置,所有定义保存在一个名为charts.xml(或国际化后的文件名如charts_zh_CN.xml,这里只考虑缺省语言)的文件内,下面是该文件的部分内容:

<?xml version="1.0" encoding="UTF-8" ?>
<charts>
    <chart id="BarChart1" >
        <title>统计图一</title>
        <legendVisible>false</legendVisible>
        <toolTipsVisible>true</toolTipsVisible>
        <type>Bar</type>
        <labelx>时间</labelx>
        <labely>数据</labely>
        <width>500</width>
        <height>360</height>
        <hql>select count(c),c.department.name from edu.pku.pub.aims.model.business.Client c group by c.department</hql>
        <description></description>
    </chart>
</charts>

可以看出,我为每个图定义了id、title、legendVisible等等属性,这些属性的意义都很明显,它们将影响统计图的数据和在页面中的表现。在程序里,我需要把这个文件里的定义读到一个注册表类ChartRegistry里,该注册表维护一个java.util.List类型的registry变量,其中每个元素是一个ChartConfig类。现在Digester该显示它的价值了。

为了方便使用Digester,我们让ChartConfig也具有统计图的每个属性(id、title、legendVisible等等),名称与charts.xml里的元素的属性(子元素)一一对应,并且都具有getter和setter方法,也就是说,ChartConfig是一个bean类。在ChartRegistry类里定义一个deregister()方法,它的作用是用Digester读入并解析指定的xml文件,代码如下;还有一个register()方法用来把ChartConfig对象加到registry里。

public void deregister(URL url) throws IOException,SAXException{
    InputStream is = new FileInputStream(url.getFile());
    Digester digester = new Digester();
    digester.push(this);
    digester.setValidating(false);
    digester.addObjectCreate("charts/chart", ChartConfig.class);
    digester.addSetProperties("charts/chart");
    digester.addBeanPropertySetter("charts/chart/legendVisible");
    digester.addBeanPropertySetter("charts/chart/toolTipsVisible");
    digester.addBeanPropertySetter("charts/chart/title");
    digester.addBeanPropertySetter("charts/chart/type");
    digester.addBeanPropertySetter("charts/chart/labelx");
    digester.addBeanPropertySetter("charts/chart/labely");
    digester.addBeanPropertySetter("charts/chart/width");
    digester.addBeanPropertySetter("charts/chart/height");
    digester.addBeanPropertySetter("charts/chart/hql");
    digester.addBeanPropertySetter("charts/chart/description");
    digester.addSetNext("charts/chart","register");
    digester.parse(is);
    Collections.sort(registry);
}

基本上来说,Digester和SAX解析xml的过程很像,它的原理就是制定一些规则,在遍历每个节点时检查是否有匹配的规则,如果有就执行对应的操作。例如,上面的代码中,“digester.addObjectCreate("charts/chart", ChartConfig.class);”这一句的作用是告诉Digester:如果遇到匹配“charts/chart”形式的节点,就执行一个“对象创建”操作,创建什么对象呢,应该创建Class为“ChartConfig.class”的对象;类似的,addSetProperties()是告诉Digester将指定节点的属性全部映射到对象的属性,在这个例子里指的就是id属性;addBeanPropertySetter()是将子节点转换为对象的属性,这个方法还可以有第二个参数,当对象的属性名和子节点的名字不一样时用来指定对象的属性名;addSetNext()是说在遇到匹配节点后,对当前对象的父对象执行一个方法,参数是当前参数,对这个例子来说就是执行ChartConfig.register(ChartConfig)方法。因此这样构造得到的Digester会把charts.xml里的每个元素转换为一个ChartConfig对象,并register到ChartRegistry里。

顺利得到了ChartRegister对象,我就可以在程序里根据它的内容构造统计图了(统计图一般使用jfreechart来生成,这里就不赘述了)。与Digester具有类似功能的工具包其实还有不少,例如Caster、Jato等等,我没有实际使用过它们,但因为我对用过的Jakarta其他项目都很满意(例如BeanUtils、HttpClient,品牌效应?),所以一开始就选择了Digester:简单方便。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/03/25/125747.html