Monday, December 29, 2008

软件项目版本号的命名格式

版本控制比较普遍的 3 种命名格式 :

一、 GNU 风格的版本号命名格式 :

主版本号 . 子版本号 [. 修正版本号 [. 编译版本号 ]]

英文对照 : Major_Version_Number.Minor_Version_Number[.Revision_Number[.Build_Number]]

示例 : 1.2.1, 2.0, 5.0.0 build-13124

二、 Windows 风格的版本号命名格式 :

主版本号 . 子版本号 [ 修正版本号 [. 编译版本号 ]]

英文对照 : Major_Version_Number.Minor_Version_Number[Revision_Number[.Build_Number]]

示例: 1.21, 2.0

三、.Net Framework 风格的版本号命名格式:

主版本号.子版本号[.编译版本号[.修正版本号]]
英文对照: Major_Version_Number.Minor_Version_Number[.Build_Number[.Revision_Number]]
版本号由二至四个部分组成:主版本号、次版本号、内部版本号和修订号。主版本号和次版本号是必选的;内部版本号和修订号是可选的,但是如果定义了修订号部分,则内部版本号就是必选的。所有定义的部分都必须是大于或等于 0 的整数。

应根据下面的约定使用这些部分:

Major :具有相同名称但不同主版本号的程序集不可互换。例如,这适用于对产品的大量重写,这些重写使得无法实现向后兼容性。

Minor :如果两个程序集的名称和主版本号相同,而次版本号不同,这指示显著增强,但照顾到了向后兼容性。例如,这适用于产品的修正版或完全向后兼容的新版本。

Build :内部版本号的不同表示对相同源所作的重新编译。这适合于更改处理器、平台或编译器的情况。

Revision :名称、主版本号和次版本号都相同但修订号不同的程序集应是完全可互换的。这适用于修复以前发布的程序集中的安全漏洞。

程序集的只有内部版本号或修订号不同的后续版本被认为是先前版本的修补程序 (Hotfix) 更新。

版本号管理策略

一、 GNU 风格的版本号管理策略:

1 .项目初版本时 , 版本号可以为 0.1 0.1.0, 也可以为 1.0 1.0.0, 如果你为人很低调 , 我想你会选择那个主版本号为 0 的方式 ;

2 .当项目在进行了局部修改或 bug 修正时 , 主版本号和子版本号都不变 , 修正版本号加 1;

3 当项目在原有的基础上增加了部分功能时 , 主版本号不变 , 子版本号加 1, 修正版本号复位为 0, 因而可以被忽略掉 ;

4 .当项目在进行了重大修改或局部修正累积较多 , 而导致项目整体发生全局变化时 , 主版本号加 1;

5 .另外 , 编译版本号一般是编译器在编译过程中自动生成的 , 我们只定义其格式 , 并不进行人为控制 .

二、 Window 下的版本号管理策略:

1 .目初版时 , 版本号为 1.0 1.00;

2. 当项目在进行了局部修改或 bug 修正时,主版本号和子版本号都不变 , 修正版本号加 1;

3. 当项目在原有的基础上增加了部分功能时 , 主版本号不变 , 子版本号加 1, 修正版本号复位为 0, 因而可以被忽略掉 ;

4. 当项目在进行了重大修改或局部修正累积较多 , 而导致项目整体发生全局变化时 , 主版本号加 1;

5. 另外 , 编译版本号一般是编译器在编译过程中自动生成的 , 我们只定义其格式 , 并不进行人为控制 .

另外 , 还可以在版本号后面加入 Alpha, Beta, Gamma, Current, RC (Release Candidate), Release, Stable 等后缀 , 在这些后缀后面还可以加入 1 位数字的版本号 .

对于用户来说 , 如果某个软件的主版本号进行了升级 , 用户还想继续那个软件 , 则发行软件的公司一般要对用户收取升级费用 ; 而如果子版本号或修正版本号发生了升级 , 一般来说是免费的 .

附: alphal 内部测试版

   beta 外部测试版

   demo 演示版

   Enhance 增强版或者加强版 属于正式版

   Free 自由版

   Full version 完全版 属于正式版

   shareware 共享版

   Release 发行版 有时间限制

   Upgrade 升级版

   Retail 零售版

   Cardware 属共享软件的一种,只要给作者回复一封电邮或明信片即可。(有的作者并由此提供注册码等),目前这种形式已不多见。

   Plus 属增强版,不过这种大部分是在程序界面及多媒体功能上增强。

   Preview 预览版

   Corporation & Enterprise 企业版

   Standard 标准版

   Mini 迷你版也叫精简版只有最基本的功能

   Premium -- 贵价版

   Professional -- 专业版

   Express -- 特别版

   Deluxe -- 豪华版

   Regged -- 已注册版

   CN -- 简体中文版

   CHT -- 繁体中文版

   EN -- 英文版

   Multilanguage -- 多语言版

注释:

此版本表示该软件仅仅是一个初步完成品,通常只在软件开发者内部交流,也有很少一部分发布给专业测试人员。一般而言,该版本软件的 bug 较多,普通用户最好不要安装。

该 版本相对于α版已有了很大的改进,消除了严重的错误,但还是存在着一些缺陷,需要经过大规模的发布测试来进一步消除。这一版本通常由软件公司免费发布,用 户可从相关的站点下载。通过一些专业爱好者的测试,将结果反馈给开发者,开发者们再进行有针对性的修改。该版本也不适合一般用户安装。

该版本已经相当成熟了,与即将发行的正式版相差无几,如果用户实在等不及了,尽可以装上一试。

试用版软件在最近的几年里颇为流行,主要是得益于互联网的迅速发展。该版本软件通常都有时间限制,过期之后用户如果希望继续使用,一般得交纳一定的费用进行注册或购买。有些试用版软件还在功能上做了一定的限制。

未 注册版与试用版极其类似,只是未注册版通常没有时间限制,在功能上相对于正式版做了一定的限制,例如绝大多数网络电话软件的注册版和未注册版,两者之间在 通话质量上有很大差距。还有些虽然在使用上与正式版毫无二致,但是动不动就会弹出一个恼人的消息框来提醒你注册,如看图软件 acdsee 、智能陈桥汉字输入软件等。

也称为演示版,在非正式版软件中,该版本的知名度最大。 demo 版仅仅集成了正式版中的几个功能,颇有点像 unregistered 。不同的是, demo 版一般不能通过升级或注册的方法变为正式版。

以上是软件正式版本推出之前的几个版本,α、β、γ可以称为测试版,大凡成熟软件总会有多个测试版,如 windows 98 的β版,前前后后将近有 10 个。这么多的测试版一方面为了最终产品尽可能地满足用户的需要,另一方面也尽量减少了软件中的 bug 。而 trial unregistered demo 有时统称为演示版,这一类版本的广告色彩较浓,颇有点先尝后买的味道,对于普通用户而言自然是可以免费尝鲜了。

不同类型的软件的正式版本通常也有区别。

该版本意味“最终释放版”,在出了一系列的测试版之后,终归会有一个正式版本,对于用户而言,购买该版本的软件绝对不会错。该版本有时也称为标准版。一般情况下, release 不会以单词形式出现在软件封面上,取而代之的是符号 (r) ,如 windows nt(r) 4.0 ms-dos(r) 6.22 等。

很显然,该版本是与 unregistered 相对的注册版。注册版、 release 和下面所讲的 standard 版一样,都是软件的正式版本,只是注册版软件的前身有很大一部分是从网上下载的。

这是最常见的标准版,不论是什么软件,标准版一定存在。标准版中包含了该软件的基本组件及一些常用功能,可以满足一般用户的需求。其价格相对高一级版本而言还是“平易近人”的。

顾名思义即为“豪华版”。豪华版通常是相对于标准版而言的,主要区别是多了几项功能,价格当然会高出一大块,不推荐一般用户购买。此版本通常是为那些追求“完美”的专业用户所准备的。

该版本型号常见于百科全书中,比较有名的是微软的 encarta 系列。 reference 是最高级别,其包含的主题、图像、影片剪辑等相对于 standard deluxe 版均有大幅增加,容量由一张光盘猛增至三张光盘,并且加入了很强的交互功能,当然价格也不菲。可以这么说,这一版本的百科全书才能算是真正的百科全书,也是发烧友们收藏的首选。

专业版是针对某些特定的开发工具软件而言的。专业版中有许多内容是标准版中所没有的,这些内容对于一个专业的软件开发人员来说是极为重要的。如微软的 visual foxpro 标准版并不具备编译成可执行文件的功能,这对于一个完整的开发项目而言显然是无法忍受的,若客户机上没有 foxpro 将不能使用。如果用专业版就没有这个问题了。

企业版是开发类软件中的极品(相当于百科全书中的 reference 版)。拥有一套这种版本的软件可以毫无障碍地开发任何级别的应用软件。如著名的 visual c++ 的企业版相对于专业版来说增加了几个附加的特性,如 sql 调试、扩展的存储过程向导、支持 as/400 ole db 的访问等。而这一版本的价格也是普通用户无法接受的。如微软的 visual studios 6.0 enterprise 中文版的价格为 23000 元。

其他版本

除了以上介绍的一些版本外,还有一些专有版本名称。

升级版的软件是不能独立使用的,该版本的软件在安装过程中会搜索原有的正式版,如果不存在,则拒绝执行下一步。如 microsoft office 2000 升级版、 windows 9x 升级版等等。

oem 版通常是捆绑在硬件中而不单独销售的版本。将自己的产品交给别的公司去卖,保留自己的著作权,双方互惠互利,一举两得。

网络版在功能、结构上远比单机版复杂,如果留心一下软件的报价,你就会发现某些软件单机版和网络版的价格相差非常大,有些网络版甚至多一个客户端口就要加不少钱。

基于商业上考虑,很多的软件都不是非常严谨的遵循这个规则的。最有名的就是微软了。例如他的 NT 系列版本。大家比较熟悉的是从 NT 4.0 开始的。 99 年推出了 windows 2000 2001 年退出了 windows xp 2003 年推出了 windows 2003 ,乍一看版本区别蛮大的,但是看他们的内部版本号就会发现,变化其实并不大,只是界面变化的大了而已。这是软件公司经常干的事情。 Window 2000 的版本号是 NT 5.0 windows xp 的版本号是 NT 5.1 windows 2003 的版本号是 NT 5.2 ,而现在的 longhorn 才是真正的 NT 6.0 (印象中是,不敢确认)。这样就可以持续的赚广大客户的钱。毕竟人的眼睛看得东西是最直观的,所以给人感觉也是变化最大的

Sunday, December 28, 2008

数字摘要

public static void main(String[] args) {
String old ="";
try {
for (int i = 0; i < 10; i++) {
String beforeDegist = "asdf";
System.out.println("摘要前:" + beforeDegist);

// 初始信息要转换成字节流的形式
byte[] plainText;

plainText = beforeDegist.getBytes("UTF-8");

// 使用getInstance("算法")来获得消息摘要,这里使用SHA-1的160位算法
// MessageDigest messageDigest =
// MessageDigest.getInstance("SHA-1");
MessageDigest messageDigest = MessageDigest.getInstance("MD5");

// System.out.println("\n" +
// messageDigest.getProvider().getInfo());

// 开始使用算法
messageDigest.update(plainText);

String afterDegist = new String(messageDigest.digest(),"UTF8");
System.out.println("摘要后:" + afterDegist.equals(old));
old = afterDegist;
// 输出算法运算结果
// StringBuffer afterDegist = new StringBuffer();
// afterDegist.append(messageDigest.digest());//, "UTF-8");


}
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

Saturday, December 27, 2008

ubuntu svn subclipse

If you are running Ubuntu 8.04 and you install Eclipse 3.4 with subclipse 1.4, you will probably get the "unable to load default svn client" error when trying to add a repository.

Here it is how I solved this problem. I assume that you have the 'subversion' and 'libsvn-java' packages already installed (if not, please do install them, and then follow the instructions).

1) Go to System --> Administration --> Software Sources
and under the "Updates" tab, check the hardy-backports updates. Close the window and update the package lists.
2) Run the update manager and update all packages (subversion will be updated from version 1.4 to version 1.5)
3) Edit the eclipse.ini file (it is located into the directory where you installed eclipse) by adding the following two lines, immediately after the "-vmargs" line

-Djava.library.path=/usr/share/java/
-Djava.library.path=/usr/lib/jni/


This should do the job, next time you start eclipse, it should correctly add your existing repository.

Saturday, December 20, 2008

任务

1.实现完整的Rank View
2.在run dialog里添加选择算法的选项,包括trantula,CBI等,以便算出Rank

Wednesday, December 17, 2008

JVM监控工具介绍

jstatd
启动jvm监控服务。它是一个基于rmi的应用,向远程机器提供本机jvm应用程序的信息。默认端口1099。
实例:jstatd -J-Djava.security.policy=my.policy

my.policy文件需要自己建立,内如如下:
grant codebase "file:$JAVA_HOME/lib/tools.jar" {
permission java.security.AllPermission;
};
这是安全策略文件,因为jdk对jvm做了jaas的安全检测,所以我们必须设置一些策略,使得jstatd被允许作网络操作

jps
列出所有的jvm实例
实例:
jps
列出本机所有的jvm实例

jps 192.168.0.77
列出远程服务器192.168.0.77机器所有的jvm实例,采用rmi协议,默认连接端口为1099
(前提是远程服务器提供jstatd服务)

输出内容如下:
jones@jones:~/data/ebook/java/j2se/jdk_gc$ jps
6286 Jps
6174 Jstat

jconsole
一个图形化界面,可以观察到java进程的gc,class,内存等信息。虽然比较直观,但是个人还是比较倾向于使用jstat命令(在最后一部分会对jstat作详细的介绍)。

jinfo(linux下特有)
观察运行中的java程序的运行环境参数:参数包括Java System属性和JVM命令行参数
实例:jinfo 2083
其中2083就是java进程id号,可以用jps得到这个id号。
输出内容太多了,不在这里一一列举,大家可以自己尝试这个命令。

jstack(linux下特有)
可以观察到jvm中当前所有线程的运行情况和线程当前状态
jstack 2083
输出内容如下:


jmap(linux下特有,也是很常用的一个命令)
观察运行中的jvm物理内存的占用情况。
参数如下:
-heap
:打印jvm heap的情况
-histo:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。
-histo:live :同上,但是只答应存活对象的情况
-permstat:打印permanent generation heap情况

命令使用:
jmap -heap 2083
可以观察到New Generation(Eden Space,From Space,To Space),tenured generation,Perm Generation的内存使用情况
输出内容:


jmap -histo 2083 | jmap -histo:live 2083
可以观察heap中所有对象的情况(heap中所有生存的对象的情况)。包括对象数量和所占空间大小。
输出内容:

写个脚本,可以很快把占用heap最大的对象找出来,对付内存泄漏特别有效。

jstat
最后要重点介绍下这个命令。
这是jdk命令中比较重要,也是相当实用的一个命令,可以观察到classloader,compiler,gc相关信息
具体参数如下:
-class:统计class loader行为信息
-compile:统计编译行为信息
-gc:统计jdk gc时heap信息
-gccapacity:统计不同的generations(不知道怎么翻译好,包括新生区,老年区,permanent区)相应的heap容量情况
-gccause:统计gc的情况,(同-gcutil)和引起gc的事件
-gcnew:统计gc时,新生代的情况
-gcnewcapacity:统计gc时,新生代heap容量
-gcold:统计gc时,老年区的情况
-gcoldcapacity:统计gc时,老年区heap容量
-gcpermcapacity:统计gc时,permanent区heap容量
-gcutil:统计gc时,heap情况
-printcompilation:不知道干什么的,一直没用过。

一般比较常用的几个参数是:
jstat -class 2083 1000 10 (每隔1秒监控一次,一共做10次)
输出内容含义如下:
Loaded Number of classes loaded.
Bytes Number of Kbytes loaded.
Unloaded Number of classes unloaded.
Bytes Number of Kbytes unloaded.
Time Time spent performing class load and unload operations.








jstat -gc 2083 2000 20(每隔2秒监控一次,共做10)
输出内容含义如下:
S0C Current survivor space 0 capacity (KB).
EC Current eden space capacity (KB).
EU Eden space utilization (KB).
OC Current old space capacity (KB).
OU Old space utilization (KB).
PC Current permanent space capacity (KB).
PU Permanent space utilization (KB).
YGC Number of young generation GC Events.
YGCT Young generation garbage collection time.
FGC Number of full GC events.
FGCT Full garbage collection time.
GCT Total garbage collection time.


















输出内容:


如果能熟练运用这些命令,尤其是在linux下,那么完全可以代替jprofile等监控工具了,谁让它收费呢。呵呵。
用命令的好处就是速度快,并且辅助于其他命令,比如grep gawk sed等,可以组装多种符合自己需求的工具。

Tuesday, December 16, 2008

统计代码行数的类

import java.io.File;
import java.io.FileNotFoundException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;


public class CountCodeLines {
static int codeLines = 0;

static int whiteLines = 0;

static int commentLines = 0;

static int tatolLines = 0;

static boolean bComment = false;

public static void main(String[] args) {

StringBuffer pathName = new StringBuffer(
"/home/xj/bin/eclipse/workspace/hku.cs.lunuo.test/src/hku/cs/test");
ComputeDirectoryAndFiles(pathName, 0);
System.out.println("Code Lines : "
+ (codeLines = tatolLines - commentLines - whiteLines));
System.out.println("White Lines : " + whiteLines);
System.out.println("Comment Lines : " + commentLines);

}

public static void ComputeDirectoryAndFiles(StringBuffer pathName, int level) {
File directory = new File(pathName.toString());
File[] files = directory.listFiles();
String prefix = "";
for (int i = 0; i < files.length; i++) {
prefix += "** ";
}
if (directory.isDirectory()) {
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()
&& files[i].getName().matches(
"^[a-zA-Z[^0-9]]\\w*.java$")) {

computeLines(files[i]);
}
if (files[i].isDirectory()) {

pathName.append("/" + files[i].getName());
level++;
ComputeDirectoryAndFiles(pathName, level);
int start = pathName.toString().length()
- files[i].getName().length() - 1;
int end = pathName.toString().length();
pathName.delete(start, end);

level--;
}
}
}
}

public static void computeLines(File file) {
BufferedReader bf = null;

try {
bf = new BufferedReader(new FileReader(file));
String lineStr = "";
while ((lineStr = bf.readLine()) != null) {
// 总行数
tatolLines++;
// 计算空行
whiteLines(lineStr);
// 统计代码行数
commendLines(lineStr);
// 计算代码的行数
// codeLines(lineStr);
}
} catch (FileNotFoundException e) {
System.out.println("文件没有找到");
} catch (IOException ee) {
System.out.println("输入输出异常 ");
} finally {
if (bf != null) {
try {
bf.close();
bf = null;
} catch (Exception e) {
System.out.println("关闭BufferReader时出错");
}
}
}
}

public static void whiteLines(String lineStr) {
if (lineStr.matches("^[\\s&&[^\\n]]*$")) {
whiteLines++;
}

}

public static void commendLines(String lineStr) {

// 判断是否是一个注释行
// 这里是单行注释的如 /*..... */或/**.... */
if (lineStr.matches("\\s*/\\*{1,}.*(\\*/).*")) {

commentLines++;
}
/**
* 这里是多行注释的
*/
// 这里的是当开始为/**或/*但是没有 */ 关闭时
else if (lineStr.matches("\\s*/\\*{1,}.*[^\\*/].*")) {

commentLines++;
bComment = true;
} else if (true == bComment) {

commentLines++;
if (lineStr.matches("\\s*[\\*/]+\\s*")) {
bComment = false;
}
} else if (lineStr.matches("^[\\s]*//.*")) {

commentLines++;
}

}

}

Monday, December 15, 2008

毕设告休5天,先考试

前景:
修改instrument使用thread而不是vm,加快速度
修改testcasemodify仅仅修改相关类,需要研究一下junit或者使用soot查看resolveclass,加快速度
从xml文件中读取suspicious文件,构造新的problems view,并根据constructure.xml结构化视图,添加marker

增加副产品中的coverage分析。

-----------------
以上需要在中期前完成
1月1号开始写报告和做PPT,10号中期答辩。

增加cfg中的分析浏览。

Saturday, December 13, 2008

Get main class name

Map a = Thread.getAllStackTraces();
for(Thread t : a.keySet()){
if ("main".equals(t.getName())){
StackTraceElement[] stack = t.getStackTrace ();
   StackTraceElement main = stack[stack.length - 1];
   String mainClass = main.getClassName ();
   System.out.println(mainClass);
}
}

Thursday, December 11, 2008

项目中counter打印的几种方法思考

项目中需要在class文件中加入probe(常见是使用静态变量保存),那么在什么时候dump out?

首先由于是测试程序,被测试的程序本身就可能会随时出错,所以该在何处dump profile 信息呢。在由别的程序调用时我们可以用try来捕获所有异常,在finally中收集信息。这就要求是在同一个jvm中。由于java中没有析构函数的说法,而garbage清理对象的时间我们也无法确定。在项目过程中,在命令行和测试底层功能时我是用上述方法解决得。

现在要把profile功能和JUnit结合在一起,这就需要想办法解决这个问题。首先,在eclipse中,一般run一个程序是在新的JVM中的。其次对JUnit的原则来说,一般是一个TestCase(指一个类中多个testXXX方法会被生成一个testcase)只测一个功能。所以在整个过程中,需要dump和clear counter。而我的plugin又是运行在eclipse的这个JVM中,如何在两个JVM中传递信息呢。简单想来,可以在在plugin注册TeseListener,当session,每个testcase启动和结束时,可以收集到基本信息。但是由于在两个JVM,事实上这是不可以收集到的。在每个TestCase上可以写setUp()和tearDown(),最简单的就是在这个过程中clear和dump。

方法1:在setUp或tearDown中写上clear,dump counter的方法。可以保存到xml文件中。再在session结束时,根据所生成的xml信息进一步处理。
好处:绝对是正确的dump clear counter信息。在这里可以很正确的处理执行顺序的问题。即使监听者能知道testcase start和finish的时间。但是可以明确看到是异步的,就是说一个监听者可能会落后testcase执行。这样的话counter就不准确了。何况其中还存在如何传递counter信息的问题。
缺点: 需要dump counter 然后由plugin重新读入。这需要涉及较慢的文件操作。其次需要user 在写testcase时按特定的格式写。

方法1.1:为了改进user的使用,考虑使用soot或者处理自动加入或者setUp和tearDown。(事实上最简单的处理是使用自己junit.jar包,在TestCase.class中加入,可以通过改变classpath实现)(简单,但是出错后容易迷惑用户)

方法2:eclipse可以设置为run新的程序时于eclipse共享一个JVM,但是eclipse上强烈说明希望不要这样做,因为可能如果程序有问题会造成eclipse崩溃。在此处可以确定不大现实使用这种方法,原因在于需要于JUnit集合,LaunchDelegate只是包装了JUnit的LaunchDelegate,在JUnit的LaunchDelegate明确是新启动JVM,就是因为测试工具当然可能是测试的程序出错。但是JUnit使用safeRunnable来保证测试程序出错不影响JUnit framework崩溃。在我的项目应该没有方法解决这个问题,由于需要集成JUnit。

方法3:使用内存数据库。使用数据库不为是一个好方法。特别是在测试大型程序时收集的数据量是十分巨大的。而我程序处理时使用HashMap来查找单个counter也会造成效率低。这样在插入probe时,可以直接往数据库写数据。当然为解决2个JVM之间传递数据,此数据库应该运行在eclipse plugin的JVM上,这样plugin可以从数据库轻易得到数据。或者再要求一个JVM,绝对不该在跑测试的JVM上,我们希望JUnit启动的JVM可以尽快down(是否可以启动多个等等原因还需要验证)。
好处:在eclipse上JVM的数据库可以不用启动的关闭。只要plugin被Launch后可以一直存在。只需要清空操作。使用内存数据库也加快了数据处理速度。并且不需要在testcase的setUp和tearDown中加入任何代码。(????考虑异步和多个profile共存的counter设计),最好的希望就是比规定用户的使用方法,只要用户书写符合JUnit写法,一切由插件在背后完成。
缺点:效率(需要验证可行性)

Why does JUnit only report the first failure in a single test?

Why does JUnit only report the first failure in a single test?

(Submitted by: J. B. Rainsberger)

Reporting multiple failures in a single test is generally a sign that the test does too much, compared to what a unit test ought to do. Usually this means either that the test is really a functional/acceptance/customer test or, if it is a unit test, then it is too big a unit test.

JUnit is designed to work best with a number of small tests. It executes each test within a separate instance of the test class. It reports failure on each test. Shared setup code is most natural when sharing between tests. This is a design decision that permeates JUnit, and when you decide to report multiple failures per test, you begin to fight against JUnit. This is not recommended.

Long tests are a design smell and indicate the likelihood of a design problem. Kent Beck is fond of saying in this case that "there is an opportunity to learn something about your design." We would like to see a pattern language develop around these problems, but it has not yet been written down.

Finally, note that a single test with multiple assertions is isomorphic to a test case with multiple tests:

One test method, three assertions:


public class MyTestCase {
@Test
public void testSomething() {
// Set up for the test, manipulating local variables
assertTrue(condition1);
assertTrue(condition2);
assertTrue(condition3);
}
}

Three test methods, one assertion each:


public class MyTestCase {
// Local variables become instance variables

@Before
public void setUp() {
// Set up for the test, manipulating instance variables
}

@Test
public void testCondition1() {
assertTrue(condition1);
}

@Test
public void testCondition2() {
assertTrue(condition2);
}

@Test
public void testCondition3() {
assertTrue(condition3);
}
}

The resulting tests use JUnit's natural execution and reporting mechanism and, failure in one test does not affect the execution of the other tests. You generally want exactly one test to fail for any given bug, if you can manage it.

Wednesday, December 10, 2008

eclipse plugin开发过程中也有几个心得

另外在实践eclipse plugin开发过程中也有几个心得:

1、如果开发plugin,所有的依赖库都要包含到 Plug-in Dependencies 中;而不能只是引入到工程中。
2、如何输出到console:
MessageConsole mc=new MessageConsole("****",null);
IConsole[] cs=new IConsole[1];
cs[0]=mc;
ConsolePlugin.getDefault().getConsoleManager().addConsoles(cs);
mc.activate();
PrintStream out=new PrintStream( mc.newOutputStream());
out.println("*******.");
3、如何获取依赖工程的输出路径:
selectedProject:当前工程---由用户选择
String[] ps= selectedProject.getRequiredProjectNames();
IWorkspace w= selectedProject.getProject().getWorkspace();
for(int i=0;i
IResource r=w.getRoot().findMember(ps[i]);
try{
IJavaProject jp=new JavaProject((IProject)r,null);
File source=new File(jp.getProject().getLocation().append(jp.getOutputLocation().removeFirstSegments(1)).toOSString());
//作你的事情.....
}catch(Exception e){
//不是javaProject
e.printStackTrace();
}
4、如何使用进度Dialog:
Shell shell = new Shell();
ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
IRunnableWithProgress thread = new SomeRunner(shell);
dialog.run(true, false, thread);
//=============================
private class SomeRunner implements IRunnableWithProgress {
public void run(IProgressMonitor monitor)throws InvocationTargetException, InterruptedException {
monitor.beginTask("一些信息", 数值-总工作量);
for(;;){
// 一些工作
monitor.worked(数值-已完成工作量); //实际中,我得情况不太相符,不明白,但差不多 :(
monitor.setTaskName("一些信息");
// 一些工作
}
monitor.done();
}
}

Tuesday, December 9, 2008

将Subclipse的中文变成英文

Q: 这几天开始搭建svn,同时我也给我的eclipse安装了Subclipse插件,可是Subclipse对国际化的支援很好,可能是根据操作系统来选 择语言,所以呢,我的英文版的eclipse就有了一个中文版的subclipse,我是想换成英文的啦,可是捣鼓了半天没有结果。
我没有发现那边可以设置,我把plugin目录下的....zh.properties删除或者换成英文都试过了,还是不行。
哪位帮助一下

A:

设置eclipse启动参数:-clean -nl en_US

即:

./eclipse -clean -nl en_US

每个项目最重要的十件事

这是我在做每个项目时的明细表,我强烈建议您也这样做。原因是:(1)新的开发者很快的就能够加快这个项目的进程,(2)用户能够容易的安装你的产品,而且也很容易去维护。

1.版本控制:应该说您需要有某种形式的版本控制系统。如今的版本控制系统应该选择Subversion ,您只需要一台服务器主机。开放源代码的项目时,我使用code.google.com ,商业项目时我使用Hosted-Projects Hosted-Projects,每月只需几美元,你在网上有一个备份。您需要检查你的repository,也包括所有的依赖库。

2.命令行编译脚本:我大部分的时间都在用Eclipse,他可以为我做所有的编译,单元测试和代码覆盖(EclEmma )。不过您需要命令行脚本去不断的build(通常是使用Ant 脚本)。你的脚本应分为:
1. 编译代码
2.编译测试
3.生成javadoc
4.运行测试与代码覆盖:我使用Emma cobertura
5.生成报告:测试的通过/失败和覆盖
6.建立一个分布的JAR。

3.连续Build:我见过很多项目,由于储存库的头部并没有被编译,导致无休止的生产力损坏。始终有一个规则,无论如何至少要让存储库编译(通过测试更为理想的)。使用持续集成服务器,例如cruisecontrolHudson。我宁愿选择Hudson, 因为安装它很简单,而且功能非常强大,为您的上层管理提供了很多很好的图形显示。您要连续获得JARs,javadoc ,测试报告,覆盖面的报告它能提供生成的脚本。对于code-base的状态基本上没有疑议,如果你的老板想尝试最新的代码,他可以下载继续Build。

4.自动分级推动:设置连续Build,如果测试通过就自动的将代码转到分级服务器。我通常会使用Tomcat 服务器,只需要更新Web-INF目录与最新的代码,Tomcat 就会自动的告知新的文件和刷新。这给了人们展示产品的平台。您可以让您的QA使用这个服务器,也可以使用单独的一个。我经常会有很多的自动化测试,所以我 将网站直接给客户,并告诉他们这是“Alpha”服务器。这是非常有用的,当您的工作的一项功能完成,客户得到这一部分,对该功能的意见能给你正面的推 动,如果到最后客户才能看到,项目就很难进行改变。

5.测试和覆盖:即使您是新的自动化测试和单元测试,我仍建议你还是要按照清单上来做。自动化的测试个给下个阶段带来了整体的进步。您从自动化的测试得到的好处是巨大的,并且关于测试的介绍也出版了书籍,所以我们不在这里详细的讲解。

6.默认的嵌入式Web服务器 :如果您正在建设一个Web应用程序,我推荐Jetty 作为您的Web容器。 Jetty 最酷的地方是您可以在您的main函数中新建服务。使用Tomcat 时,你要导入一个war文件,并将它添加到Tomcat 的目录中,然后等待Tomacat “安装” ,这样就会比较麻烦。但使用Jetty 您可以通过一个main函数开始您的应用(就像一个正常的桌面APP )并通过你的main函数来启动Jetty 。您的开发者更容易去开发/调试,您的客户也更容易去部署。

7.默认情况下嵌入式数据库 :没有什么比一个复杂的数据库安装说明更复杂。所以我使用一个嵌入式数据库,例如HSQLDB 。 在开发和测试中,我让系统采用一个内存数据库,然后我才使用这个数据库的硬盘版本。 但在这两种情况下客户/开发做的没有不同,这是最重要的。如果生产环境需要大负载,我再提供命令行选项,切换到外部数据库如Oracle数据库。

8.自动安装模式 :应用程序启动的第一件事就检查schema是否安装。 如果没有它会自动安装模式。 我通常使用的Hibernate ,如果需要他会为我做所有的事,升级架构,以较新的版本。所以在数据库中我从来没有担心过架构问题。

9.guice 依赖注入 :如果您想要一个可维护性,可测性和容易理解的code-base,您需要使用依赖注入 ( DI )作为一个设计模式。一个自动化的DI框架,使您的工作更轻松。我使用guice我想这是最先进的DI投资,同样还有picocontainerspring

10.易于安装/运行 :现在考虑的是让顾客更容易的去尝试你的产品。 我喜欢给我的客户看一个JAR文件,并告诉他们“双击”来启动JVM , Web服务器,数据库,安装架构,并推出网络浏览器(基于本地)。或您的应用通过Java Web Start 开始,这样你就可以在web页面中一键式安装,这样就不需要安装手册了。

a)把主要class放在JAR MANIFAST,这样只要双击就可以加载你的应用。

b)把所有的JAR依赖放在主要的JAR文件中,使用JarJar 或者One-Jar-Classloader

c)给应用提供:内存数据;安装schema; 加载Web的容器; 安装自动提供一个admin用户等等……

这给您带来了什么好处?您将有快乐和富有成效的开发流程和快乐的顾客,而且可以很容易为你的产品吸引到客户

Monday, December 8, 2008

Lunuo Plugin开发总结(一)

作为新手入行开发eclipse插件,到现在为止初步了解了eclipse,osgi,RCP,SWT/JFace等一些知识。在这过程中,给我最大的帮助的是这本《Contributing to eclipse Principle, Patterns, and Plug-Ins》,这本书的作者是大名鼎鼎的Erich Gamma,JUnit的开发者,在书中demo的开发就是JUnit-Plugin的开发,作者用了Test Driven Development 和Pattern Design的观点和方法。让我在学习的过程中深深为精彩的开发过程吸引。还有《eclipse Building Commercial-Quality Plug-Ins》,这本书在介绍eclipse plugin上有更加详细的细节,但是整本书读起来就不像前本这么朗朗上口了。

本个月开期的半个月,花费精力在验证Fault Automatical Location上了,需要用到Program Dependence Graph of java program。根据图来算出rank最大的点离真正错误的点的距离,来统计的判断location的方法效率。通过查找资料发现indus可以提供现有的功能,但是由于缺乏文档,并且PDG的方法本身就存在很多问题等其他原因我并没有深入。而是在一位博士的指点下,改成用cost来计算。这种方法就简单多了。也不需要其他信息,在现有基础上就可完成了。

于是下半个月开始开发eclipse插件。希望可以完成基础的eclipse插件框架和整体构想。在开发途中遇到最大的困难便是Soot在eclipse插件里时,整个classloader产生变化,导致找不到类,soot反复告知我要正确设置classpath。于是我开始研究起了java的classloader机制,我搜集了网上很多的关于介绍java的classloader和eclipse框架的classloader的文章,已收集在这个blog中了。但是最后我还是没有解决这个问题。我大致了解了整个机制,但是却对于soot的classloader望而却步,soot有3万多行高质量的代码,简直令我望而生畏了。于是我采用了JUnit开发时使用的通过eclipse新启动一个VM的方法来运行程序,这样就好比完全使用soot在命令行运行类似了。由于启动的关闭VM需要消耗时间长,造成eclipseUI类似停顿的现象出现。而由于我在plugin开发时使用的是类似auto incremental builder,我希望整个instrument的过程能无缝的结合到eclipse中,只要修改了java程序,就会自动的开始instrument,类似jdt中的javabuilder。但是这样的话就需要频繁的启动的关闭新的VM来运行soot,这样造成eclipseUI停顿是不可接受了。我无法搞清楚UI停顿的问题,起码尝试了5,6种方法也没有解决。反而是通过自己试的过程中解决了这个问题。在调试过程时,我一直好奇为什么我另开线程也仍然解决不了这个问题。似乎只有在UI线程调用非UI线程才不会等在非UI线程完成。但是由于autobuilder自然是在非UI线程,于是我发现通过启动线程后sleep线程,那么autobuilder线程就会自己返回了。这是我想到线程的知识时尝试的方法。但是VM的启动和关闭仍然会是一个不好的体验。因此我认为可以让此VM持续开着,设立成C/S模型来运行Instrument会是一个很好的选择,并且通过这样可以远程部署和运行大量的测试案例。这样多个开发人员可以同时使用服务器端高性能的机器来跑繁重的test过程。因为我认为这个工具的意义在于测试代码量巨大的Application,通过手工查找是不现实的。当然最终我是需要跟JUnit结合的。自然也是要支持很好的单元测试了。

关于eclipse的远程调试,eclipse支持远程调试,在eclipse的launch框架中,可以launch新的vm运行在debug模式在,再通过socket能把调试信息推回到eclipse。由于我开发时就涉及到这个问题,所以我查找的资料来实现这个过程。但是至今我没运行成功。

在完成通过新启动VM来运行soot后,我不死心的跟soot作者 Eric联系了一下。当Eric肯定了soot是通过URLClassLoader来载入类和需要设置sootclasspath时,我恍然大悟明白了soot的载入过程。而sootplugin里用到的方法简直迷惑了我,我一直认为Threadcontextclassloader的作用是被sootclassloader当作parent的但是事实上是不是的。soot可能自己构建了classloader继承体系。只要设置sootclassloader后即可。我在原先的尝试过程中使用过,但是我忘了设置了jre/lib和jre/lib/ext的的路径,本来这个是通过systemclassloader来装载的,但是在soot调用过程中soot可能区分这个模式,也需要自己来装载。我十分懊恼我忽视了soot的exception中can't find class ,一种是我自己的类,一种的系统类的错误。这造成了我花费了大量精力去学习了javaclassloader,而实际上,soot调用和设置很简单能完成。可能是我太自信自己能解决问题吧,我直到最后通过别的方法解决后才不死心的给Eric发了我的代码,而他从他的角度帮我否定了一些可能性后,我们短短交流了一会就解决了这个问题。当然通过线程的方式启动soot可以很好的避免vm启动的时间。但是使用C/S模式也有一定的用处,所以我决定把这个功能也集成到自己开发框架了。这需要大量的关于socket和classloader,线程的知识,大致想法是模仿tomcat,jboss之类的服务器,采用同样的方法来支持class的hot swap。

We Have Lift-off: The Launching Framework in Eclipse

We Have Lift-off: The Launching Framework in Eclipse


http://www.eclipse.org/articles/Article-Launch-Framework/launch.html

Sunday, December 7, 2008

Eclipse 3.4 Hidden Treasures

Eclipse 3.4 "Ganymede" will be released in the upcoming days. I've been working with the RC builds for some time now and I like it. Eclipse 3.4 is a better IDE and a more robust platform than its' predecessor. In this post, I've gathered some new features which I like and may be "off the beaten path".

Rich Hovers

This is not a hidden feature, but I had to mention it. The Java Development Tools include some interesting enhancements in this release. The greatest innovation is the Java Editor Breadcrumbs. It looks cool, but, after using it for some time, I didn't find it very useful. The Rich Hovers, on the other hand, are very useful. I especially like the Javadoc hovers. There's an emphasis in making the documentation accessible and that's a very positive improvement.

Javadoc Rich Hover

Another useful rich hover is the Java debug hover which makes it easier to view the contents of compound objects without having to copy them to the expressions view. You can use the preferences to determine whether the behavior of the "enriched hovers".

Debug Rich Hover

The Dropins Folder

If you paid attention to the hype around Eclipse 3.4, you've heard the term P2. In simple terms, it's a new way of deploying Eclipse applications and plug-ins. For the average Eclipse user, the most noticeable change will be a new "Software Update" dialog which replaces the previous "Find & Install..." and "Manage Configuration" duo. It's a much better UI and it's a promising future to Eclipse updates.

Part of this change is "the dropins folder". This folder is located in your Eclipse distribution folder and it is initially empty. In this folder you can manually drop features and plugins which will be installed once you restart Eclipse. No more restarting with "-clean" option. There's also an option of using this folder to link to a central location of plugins, which can be shared among several Eclipse installations. I'll write a dedicated post about it in the future.

Templates View

One of the late features to make it into the Eclipse 3.4. Templates can be inserted while coding, usually using the content assistant. Adding and editing templates was possible before. In Eclipse 3.4 there's a new view which shows all possible templates and makes it easier to add new templates. There are also some new parameters which can be used when composing template. Templates are already an important feature. The templates view makes this feature even more user friendly, especially for customizations.
The view is found in the views under "General" - "Templates". Look in the Eclipse help for more details on the template variables.

Templates View

Format Only Edited Lines


The "Save Actions" is one of my favorite features in Eclipse 3.3 (read my original post about it). In Eclipse 3.4 there are several improvements including some new formatting features. One of the complaints against the Save Actions feature was that it will change the entire file, thus, making it very hard to compare to previous revisions when using a source control. It can become very annoying if some team members use it and some don't.

For that purpose there's a new feature which allows changing just the edited lines, keeping the rest of the file intact. I highly recommend using the Save Actions feature. It is inactive by default, so go ahead and activate it.

Enabling Format Edited Lines Only

Outline View Drag & Drop

This is a true hidden treasure. You can now easily rearrange your source code by dragging and dropping elements in the outline view. AFAIK, this currently works for Java and for XMLs. To use it, make sure the "sort" option is not selected and just drag & drop.


Outline View Java Outline View XML

Plug-in Spy


There are many new features in the Plug-in Development Environment (PDE) project which makes writing and deploying Eclipse plugins much easier. The Plug-in spy is a true hidden gem in this stack. One of the best ways to learn how to write plug-ins is to read the code of existing plug-ins. Naturally, the organic Eclipse plug-ins make great candidates. You see a view and you want to create something similar. The question is: how do you find the code behind that view?

This is where the plug-in spy comes into play. Invoke the plug-in spy and you'll get all the information that you need to start exploring the existing code. The plug-in spy can be invoked using the keyboard shortcut Shit+Option+F1 (Alt+Shift+F1 on Windows). It works on views and in dialogs as well, including the wizard and preferences dialog.

Plugin Spy

Error Log View


The error log view is not new. It shows platform errors and informational messages. It was greatly improved in Eclipse 3.4, with a search box and a grouping feature. It's all good, but, there's one cool feature tucked away in the toolbar. You can quickly use the log view to view the logs of workspaces you launched when running or debugging plug-ins. It shows all the defined launch configuration. Each launch configuration is associated with a runtime workspace. The log of this workspace will be shown when that configuration is selected.

Error Log View

Export/Import Launch Configurations

Launch configurations are an important part of the workspace. Every once in a while I start over and create a new workspace after having too much garbage in the old one. Importing the projects is a breeze. It's the configuration that takes time. Most of the configuration can be exported and imported and now the launch configuration are included. Since creating launch configurations can be time consuming, this is a much needed feature.

Start from the "File" menu and select the "Export" feature. Select the "Launch Configurations" and in the next dialog box select the configurations to export. The result is a folder of XMLs which can be later imported to any workspace

Launch Configuration Export

God is in the Details

The features I mentioned above are mostly major features. Eclipse 3.4 also includes an assortment of small improvements for making your life easier. The content assistant, for example, is improved, and it works in cases where it didn't before. Argument name guessing is much improved and can now also guess methods which may be called to produce the arguments (e.g. getters). There are many new feature you'll discover once you start using the new version. "Ganymede" is a great release for the Eclipse platform.

Saturday, December 6, 2008

在eclipse插件里添加property page

添加property page,实现on/off auto instrument 选项;一个项目包的列表,可以选择其中某些包需要instrument;一个filter,快速选择包名

Lunuo plugin使用注意1

使用Lunuo Plugin时,要打印出profiling信息时,一定要注意导入的包是跟插件目录同一个,否则由于classloader的机制,会造成静态的counter是不同的对象

Thursday, December 4, 2008

Eclipse - a tale of two VMs (and many classloaders)

When starting off with Eclipse plugin development or rich client platform development, you're more than likely to run into issues like ClassNotFoundException or problems with the Java command line and properties like java.endorsed.dirs.

Most often, these problems arise because many Eclipse developers don't realise the magic that lets Eclipse do its work. Amongst these are the fact that there's actually two processes under the covers, and that each bundle has its own classloader. Once you understand how these fit together, debugging problems may be somewhat easier.

Eclipse Boot Process

Why does Eclipse need two processes to run? The simple answer is that an Eclipse application needs to be run with several system properties set in order to function properly. (These include parameters like osgi.configuration.area and osgi.instance.area.) It's a key requirement in the way that Eclipse starts up, because it needs to know where to put both temporary and persistent data that's not stored directly in one of your project locations. (Examples include your preferences and the workbench history, which tracks changes to files that you have made.)

Many other tools use a shell script to launch an application with a particular classpath; not only IDEs (like NetBeans) but also server-side tools (Maven, Ant, CruiseControl ...). Most of the time, these shell scripts use shell-specific routines to add entries to a classpath (e.g. for i in lib/*.jar), and on the whole, this approach works. But some operating systems aren't as expressive as others, and unless you have the ability to add other complex logic to the script via external programs, it may not be possible to do everything that you'd want. It also means that you potentially have to test the shell script on other operating systems to determine whether it's correct or not; though generally if it works on one Unix system, it will work on others too.

To solve this issue (and others), Eclipse doesn't use a shell script to boot itself. Instead, it uses a native executable (eclipse.exe or eclipse), to parse the command-line arguments (like -vmargs) and make them available to the OSGi platform. Importantly, it calculates defaults for values that aren't overridden. It then fires up the OSGi platform in a new process (the platform VM), and hands the rest of the booting process over.

You may wonder why it fires up a new process, rather than continuing booting by incorporating a VM in the same one. There's two reasons for this: firstly, the OSGi platform that's being launched can call System.exit() without taking down the launcher (c.f. the JavaDoc task in Ant, or the fork attribute of the Java task). The second (perhaps more important) one is that it allows the OSGi platform to be launched with a different set of parameters -- or even use a specfic version of the VM. The launcher can run on many versions of the Java VM, and the launcher can guarantee that your platform code will run in a known VM (for example, by shipping one with your code and placing it in the jre/ directory).

The launcher also reads a variety of configuration files; eclipse.ini, if present, is a newline separated list of flags that are passed into the platform VM. The -vmargs flag is used to pass any subsequent arguments as VM arguments rather than normal arguments.

All of this means that when launching Eclipse via startup.jar the arguments and extra options are effectively ignored by the actual Eclipse runtime instance. You may think that:

eclipse -Xmx1024m

would give you a huge Eclipse memory runtime, but in actual fact, all you're doing is specifying an argument which is ignored by the eclipse launcher. The native launcher deals with some arguments directly; but otherwise, they're passed on verbatim to the Java launcher org.eclipse.core.launcher.Main where the real work is done. The arguments that the native launcher explicitly deals with are:

-vmargs
Arguments following this are passed directly into the platform VM's Java executable, before the class name. It must be the last argument, since anything following is put on the command line. Importantly, spaces need to be appropriately quoted or escaped since otherwise you may get the dreaded "Program/Eclipse cannot be located", which is usually because one of the VM arguments have C:\Program Files\Eclipse in them. This is available from the eclipse.vmargs system property, but it cannot be changed once the VM is started.
-vm
Full path to a Java executable that can be executed. The system path is searched if this is not specified. This is available from the eclipse.vm system property, but it cannot be changed once the VM is started.

To run Eclipse with a larger memory area, you need to pass the -vmargs to the launcher, followed by the options that you want:

eclipse arg1 arg2 arg3 -vmargs -Xmx1024m

this instructs the launcher to create the platform VM, passing in arguments arg1 arg2 arg3 and setting up the platform VM with -Xmx1024m. However, we could equally have had a file called eclipse.ini:

arg1
arg2
arg3
-vmargs
-Xmx1024m

which would have the same effect. The launcher looks for application.ini and eclipse.ini to derive these parameters, if your executable is called application.exe.

The eclipse launcher then fires up a VM, which uses org.eclipse.core.launcher.Main (from startup.jar) to instigate the next phase of the boot process. The Main launcher sets up specific areas that are needed by the OSGi platform, including setting up the properties such as where the configuration area will be. It then boots the OSGi platform, which reads which plugins to start from the osgi.bundles entry from the config.ini, and applies product branding from -product and OS/Language specific settings from -os and -nl. Once that's running, the Eclipse platform task over which locates and loads the bundle associated with the -application or -feature arguments. Certain plugins (like org.eclipse.core.resources) interpret specific command line arguments (like -refresh), but this is the exception rather than the rule.

If you want to embed Eclipse into another Java process, you can use the EclipseStarter class, as long as you supply the appropriate entries. That will fire up all the necessary plugin support to kick your application off. There's also WebStartMain that can be used to kick off an Eclipse install via Java WebStart (although with some limitations).

There's a full list of the arguments and when they are interpreted in the Eclipse help pages under "runtime options", and can be summarised as:

Handled by eclipse.exe Handled by org.eclipse.core.launcher.Main Handed by the OSGi platform Handled by the Eclipse platform
  • -vmargs
  • -vm
  • -name
  • -noSplash
  • -startup
  • -configuration
  • -endSplash
  • -framework
  • -initialize
  • -install
  • -noSplash
  • -showSplash
  • -arch
  • -clean
  • -console
  • -data
  • -debug
  • -dev
  • -nl
  • -os
  • -product
  • -user
  • -ws
  • -application
  • -feature
  • -keyring
  • -noLazyRegistryCacheLoading
  • -noRegistryCache
  • -password
  • -pluginCustomization

ClassLoaders, Bundles and Buddies

You may recall that Java uses a ClassLoader to bring classes into the VM. Most of the time, you probably don't need to know anything about how they work (or even that they exist) but every class in Java is loaded via a ClassLoader. Its job is to turn a sequence of bytes into a java.lang.Class. Where those bytes come from doesn't really matter; and it's this fact (and the URLClassLoader) that brought Java to fame with Applets in the first place. Mostly, client side applications don't need to worry about the ClassLoader: they just set up a classpath and It Just Works.

Server-side applications (J2EE servers and others like Tomcat) have long used ClassLoaders to enable classes to be loaded on demand from different structures (like WAR formats). As well as providing a way of accessing the classes, the ClassLoader also provides another key benefit; it separates out classes with the same name. Every class in Java has an associated ClassLoader (and you can find out which by obj.getClass().getClassLoader() if you want).

Importantly, a class name is unique only within its ClassLoader. That means it's possible to have two classes with the same name loaded into a VM at once, provided that they have two separate ClassLoaders that loaded them. Whilst this may sound freaky and unnatural, in fact it's how application servers like Tomcat can host any Web application and allow hot deployment. It simply creates a new ClassLoader, loads in the classes (again) and runs the new version. Without this ability, an application server would not be able to reload a web application without having to restart the server in its entirety.

Eclipse uses this to its advantage. One of the key features of Eclipse 3.0 was the ability to stop and start a bundle whilst the platform continued running. (It's used when you update, and choose the 'Apply changes now' instead of restarting Eclipse, amongst other things.) Perhaps more importantly, Eclipse allows multiple different versions of a bundle to be loaded at one time, which might be useful if you have many parts of your plugin relying either on 2.7.1 or 2.8.0 of Xerces.

To do this, each bundle (OSGi) or plugin (Eclipse) has its own ClassLoader. When a bundle is started, it gets given its own ClassLoader for its lifetime. When it's stopped and restarted, a new ClassLoader is created. This allows the new bundle to have a different set of classes than the previous version.

The OSGi platform translates the Manifest.MF present in every (OSGi-enabled) plugin into a hierarchy of ClassLoaders. When you can't load a class from your plugin directly, the bundle mechanism searches through all the required bundles to see if it can find the class there instead. Packages that depend on the same bundles (e.g. org.eclipse.ui) will result in the same class being loaded, because it comes from org.eclipse.ui's ClassLoader.

This causes confusion for new developers to the Eclipse platform. It doesn't matter what's in the CLASSPATH environment variable, or what's specified on the -classpath argument, because pretty much every ClassLoader only sees what its Manifest.MF tells it to see. If there's no dependency at the bundle level, then as far as Eclipse is concerned, it isn't there. (Incidentally, that's how the .internal. packages work; whilst they exist in the bundles, and can be loaded by the bundle's ClassLoader, the Eclipse class loading mechanism hides any package that's not explicitly mentioned in the Export-Package header in the Manifest.MF. It also doesn't help that the PDE runtime only consults the entries in the .classpath when running/debugging inside Eclipse, and it's only when you try to export the product that the Manifest.MF is consulted, often resulting in errors. A ClassNotFoundException during product/plugin exporting is almost always down to the fact that it's mentioned in the .classpath (Java Build path) but not the Manifest.MF.

A larger problem occurs when using libraries that expect to be able to see your code as well as their own. This includes libraries such as log4j and generator tools such as JibX that both supply services and consume code. Because Eclipse uses a partitioned class loading mechanism, the ClassLoader of the org.apache.log4j can't see the classes defined in org.example.myplugin, since it's a separate ClassLoader. As a result, log4j can't use your supplied class for logging, and complains.

This is the purpose of the buddy class loading in Eclipse. It is designed to work around the issues that can be found in this type of problem. (It's quite similar to the options that you can configure with some of the application servers, such as "parent first" and "child first" lookup policies.) The buddy policy (specified with the Eclipse-BuddyPolicy entry) takes one of:

dependent
search the dependent's classloaders
global
search the global packages exported via Export-Package
app
search the application's classloader
ext
search the extension's classloader
boot
search the boot's classloader
registered
search the registered buddies classloaders
Buddy classloading diagram
Figure 1. Buddy classloader diagram

Whilst all of these are generally fine, if you want to use a tool like log4j then it generally boils down to:

# log4j Manifest.MF
Bundle-Name: org.apache.log4j
Bundle-Version: 1.2.13
...
Eclipse-BuddyPolicy: registered

This says "If anyone registers with me, and I need to find a class that I can't otherwise find, I'll check with them before failing". You also need to define in your own plugin that you want to register with it:

# myplugin Manifest.MF
Bundle-Name: org.example.myplugin
Bundle-Version: 1.0.0
Requires-Bundle: org.apache.log4j,...
...
Eclipse-RegisterBuddy: org.apache.log4j

This now makes the org.apache.log4j and org.example.myplugin buddies. Of course, our org.example.myplugin always depended on org.apache.log4j before, but now we've added the extra hook back that allows the log4j to see our classes as well. In essence, it's like adding Requires-Bundle: org.example.myplugin to the org.apache.log4j bundle, except that obviously you can't do that (it would create a circular reference) and I doubt that Ceki G?lc? would have had the foresight to depend on myplugin in advance. But by putting Eclipse-BuddyPolicy: registered into the definition, now any future bundle can plug in and register its code with org.apache.log4j.

It's probably a pretty good idea to add the Eclipse-BuddyPolicy: registered to your plugin now, because in the future someone might need it.

Native code and classloaders

As well as resolving classes, the ClassLoader is also responsible for finding native code in response to a System.loadLibrary() call. Whilst it doesn't actually perform the loading of the DLL itself, it does figure out where the DLL is and passes a full path to the operating system to install the code. In the case of packed OSGi bundles, this call automatically extracts a copy of the DLL to a temporary location before passing the location back to the OS.

It's also worth bearing in mind that the DLL loaded may have other dependencies. Unfortunately, although Eclipse knows where to look for further dependencies (e.g. the java.library.path or the contents of a packed Jar file), once the operating system only knows about the PATH or LD_LIBRARY_PATH environment variables (depending on your operating system). So if you have a.dll which depends on b.dll, and you do System.loadLibrary("a"), it will fail with an UnsatisfiedLinkError. That's because although Eclipse knows where to find the dependent dll, the operating system doesn't know where to look for b.dll and gives up. Instead, if you did System.loadLibrary("b"); System.loadLibrary("a"); then the DLL is available in memory for a.dll to trivially hook up against, and it all works. The moral of the story is always load the DLLs in the order in which they are dependent.

It's worth bearing in mind that Windows and Java have some issues when it comes to loading DLLs. A DLL can only be loaded once in a VM and only associated with one ClassLoader. If you have two ClassLoaders, and they attempt to load the same DLL, then the second one will fail and not be able to execute any native code. As a result of this, you cannot have two bundles that attempt to load the same native code in Eclipse. This will also apply if the bundle is updated or restarted, because the old ClassLoader may retain a reference to the DLL whilst the new one starts. This is a Windows-specific problem that doesn't appear to manifest itself on other operating systems.

Lastly, whilst Eclipse 2.1 used a combination of directories (ws/os/arch, such as win32/win32/x86/swt1234.dll) for storing native code, this is no longer the preferred way of doing it. Instead, native code should be at the root level of the plugin, probably with a suffix like swt1234-win32x86.dll. Even better, instead of doing System.loadLibrary(), you can define Bundle-NativeCode: swt1234.dll and the OSGi platform will load it automatically. You can either provide per-os fragments, or split them apart and use a platform filter (such as Eclipse-PlatformFilter: (& (osgi.ws=win32) (osgi.os=win32) (osgi.arch=x86))).

Conclusion

Once you understand how the processes work within the Eclipse boot sequence, and the fact that it utilises many classloaders, some of the ClassNotFoundExceptions become easier to understand. Specifying arguments works on a command line java invocation because you're specifying the VM on the command line (and most of the time, only using one ClassLoader too). In the Eclipse world, if you want to affect Eclipse's VM, you need to remember to prefix any arguments with -vmargs, either on the command line or on via the eclipse.ini file.

It's also worth checking when you have ClassNotFoundExceptions when exporting or running an application outside of Eclipse's debugger, that you have the dependencies listed in Manifest.MF and not just in the .classpath (Java Build Path). In fact, you shouldn't need to put any extra entries in the .classpath if you're building a plugin project, because the entries in Manifest.MF are automatically available in the .classpath through the "Plugin Dependencies" classpath container.

Lastly, if you're using native code in an Eclipse application, it's a very good idea to isolate the native code in its own bundle. Any plugin that needs to use that native functionality can then express a dependency on that bundle, which allows it to be shared by as many bundles as need it. That way, if you have any non-native updates to your bundle, you don't need to restart Eclipse to see those changes. Of course, if you have native bundle updates then you have to restart anyway to get the benefit.