很久很久以前

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  34 随笔 :: 4 文章 :: 17 评论 :: 0 Trackbacks

2006年1月12日 #

Winsock 10053错误分析

 WSAGetLastError可能会返回10053错误,查msdn的解释是:

WSAECONNABORTED 10053

Software caused connection abort.

An established connection was aborted by the software in your host computer, possibly due to a data transmission time-out or protocol error.

神马?软件原因造成的连接中断,这是什么意思,不跟没说一样的么?
google一下呗

Berkeley description:

A connection abort was caused internal to your host machine. The software caused

a connection abort because there is no space on the socket’s queue and the socket

 cannot receive further connections.

       

WinSock description:

Partly the same as Berkeley. The error can occur when the local network system aborts

a connection. This would occur if WinSock aborts an established connection after data

retransmission fails  (receiver never acknowledges data sent on a datastream socket).

       

TCP/IP scenario:

A connection will timeout if the local system doesn’t receive an (ACK)nowledgement for

data sent.  It would also timeout if a (FIN)ish TCP packet is not ACK’d

(and even if the FIN is ACK’d, it will eventually timeout if a FIN is not returned).

 

伯克利说这种连接中断是因为宿主机器的内部原因,因为软件导致的连接中断,可能是因为socket的队列满并且这个socket不能接收更多的连接了。
这还不如不说,越说越糊涂了。
winsocket的描述,似乎还靠谱一些,这种错误一般发生在一个建立的连接被重发失败的情况下产生,接收方没有响应数据发回来。但还是比较模糊。
再看看tcp ip标准文档的说法,如果本地系统没有收到发送数据的响应(ack)那么这连接就会超时。如果tcp的fin包没有被ack(或者fin包被ack了但fin没有返回)那么也会超时。但是,但是,超时跟这个10053有神马关系?
再看后续的解释:
从参考1中找到如下的描述:

The Scenario: 
An HTTP POST is to be sent to an HTTP server.
The server begins reading the POST and notices that the HTTP request header is invalid.
It immediately sends an HTTP response (with an error status, perhaps status=400) and closes the connection without trying to continue reading the remainder of the HTTP request that is forthcoming.

Meanwhile, the client is still happily writing the remainder of the HTTP request to the socket. (Remember a TCP/IP socket connection needs to be closed from both sides. In this case, the server has closed its side, but the client is still pumping data into the half-open connection.)
The client finishes writing the HTTP POST to the socket — meaning that data has been buffered to Winsock. The client application then tries to read the HTTP response, but it cannot because the outgoing retransmission (of the buffered data by WinSock) failed and the socket connection was shutdown on the client side (by Winsock). Even though the HTTP server sent the response, it is lost and cannot be retrieved. The error your application will receive when
trying to read the HTTP response on the socket is WSAECONNABORTED. The word "software" in any of the above error messages refers to "WinSock".

Go back and re-read the original error explanations. Hopefully, after that explanation, you’ll say "Aha! I understand what they’re talking about!".

 

啊哈,又有http了,大概意思就是http server收到请求了,但发现有问题,那么回一个http错误码,然后就关闭了socket,但与此同时,client端还在很开心地向socket写数据,注意哦,tcp是全双工的。client写完毕后,实际上数据只是放到了发送方的缓冲区中,不一定已经发出去了,如果写得不好的程序,这个时候就开始从socket读数据了,这时候就会产生一个WSACONNECTABORTED错误,windows上对应的就是10053错误。

但这个解释实际上是不能让人满意的,只是举出了一种场景,但为什么会产生还没有解释。后面又搜到了个参考2,首先解释10053错误是收到fin后client会放弃发送缓冲区中的数据,同时上报错误。虽然说法还有点一头雾水。

不过这两个参考给我们一个思路,重现这个问题。

于是简单写个测试用的c-s程序,大概流程如下

 

 

图1 CS程序简化流程图

这个简单程序演示如何出现10053错误(以及10054错误)。

如果server在收到client发送的数据后立即关闭socket,那么client再读时,会收到10053错误;如果server收到发送数据后,立即crash,那么随后client再读取时会收到10054错误。

ok,能够重现场景了,那么我们来分析一下更细节的方面,网络问题自然是抓包,本问题处理抓包还要看一下tcp的状态以便辅助分析,我们在client端每次操作之前都打印当前的tcp状态。

下面是client端发送记录和对应的netstat情况

图2 10053错误client端tcp状态流转

client在发送之前tcp状态是established,在发送之后,server会立即关闭,tcp状态也变为close_wait,但这只是单方向的关闭,client可以继续发数据,但client发送后,server立即退出了,导致后续recv会失败并且返回10053。对应抓包情况如下:



图3 10053错误client端tcp抓包

整个通信过程如下:
1-3.三次握手建立连接
4.客户端(10.10.86.93)向服务器端(10.10.86.98)发送数据,1字节
5.server 中止 发送fin(同时ack之前那个push)
6.client ack 那个fin
7.client再发送两个字节
8.server此时已经关闭socket,属于非正常情况,回复复位命令

整个过程可以重现10053情况,tcp发送分组数据的情况也一目了然,事情到此就可以了么?显然不是,你也看到了后面还有很多文字,不知此时你心中的问题是否跟我一样,先说我自己的吧,通过抓包发现这里的异常关闭有个reset,但reset一般是10054(Connection reset by peer)的错误,那么10053与10054的区别在哪里。要搞清楚问题也不难,重现场景抓包分析。
以下是修改上面的cs程序,在client发送的1字节包后,立即crash,这导致的问题是操作系统会立即回收所有资源,包括socket资源。




图4 10054错误client端tcp状态流转

可以看到在crash之前这个tcp都是established状态。crash之后,client端接收数据时会收到10054错误,场景重现了,我们再看一下抓包情况



图5 10054错误client端tcp抓包

这个抓包情况跟10053很像,1-7也同10053,在8时,client收到server发过来的reset,表示当前连接被强制复位了。
对比10053和10054可以发现,如果srv返回fin标志后再reset那么对应的错误就是10053,如果直接reset就是10054错误。回过头来在看参考2中的说法也就有点感觉了。

总结一下:
1.遇到不了解的问题,google是非常好的方法
2.对于一般问题,重现之很重要,可以反复发现问题并验证问题。自己写程序或者搭环境尽量重现。
3.网络问题抓包是利器,包括各种工具的使用netstat wireshark ping traceroute等。
4.多重问题对比其中的差异,这里对比10053错误和10054错误。
5.理论基础要搭好,本次问题主要是tcp的异常断开问题,熟悉tcp断开的半关闭和复位逻辑,不过理论还是理论,同样是复位在不同场景下的错误码不同。并且实现上也跟具体的操作系统相关。
6.实际工作中,
10053错误时,用户主要是处于透明代理情况,那么这一般是又有用户所在的代理服务器异常关闭导致的,可能跟我们的离线文件私有协议被用户所在的代理服务器拒绝掉导致的。

7.回过头来在看一开始的解释,所谓软件原因造成的连接终端,就是本例子中,server端在shoutdown本方向传输时,立即关闭了socket,导致本应该等待对方发送fin来完全结束的正常逻辑被打破,编程单方向强制中止本次tcp,导致client端之后向上报错,就是所谓的10053错误了,这里的软件就是server端的那个程序。(不过也有种说法是,客户端发送错误数据,导致server端保护机制而强制关闭)


参考:

  1. http://www.chilkatsoft.com/p/p_299.asp   
  2. http://bbs.csdn.net/topics/360024280#post-361829232
  3. 《TCP/IP详解(卷一)》18章 TCP连接的建立和中止

 

 

posted @ 2013-11-28 11:22 Long Long Ago 阅读(28916) | 评论 (1)编辑 收藏

最近改造文件传输,参考libcurl,考虑到他支持那么多协议,但我只关心http的,所以考虑是否可以只生成http支持的版本,查了一下,果然可以。
下载,如果不需要最新的,那么只要下载个zip包就好了。
通过download wizard可以指引你下载不同的版本:http://curl.haxx.se/dlwiz/
选择
source code - 平台无关- 找到最新版本下载,解压缩
编译比较简单有build指令
不过我从vs目录下找到一个2005版本的vcproj文件,用这个也可以编译,不过要设置一下include目录为../../../include
 上面是废话了,关键的怎么值生成对http的支持呢
只要在编译指令中增加定义HTTP_ONLY宏就可以了,就这么简单。
详细说明在这里http://curl.haxx.se/docs/install.html
posted @ 2013-11-21 20:40 Long Long Ago 阅读(662) | 评论 (0)编辑 收藏

路由器软件部分的几个概念:CFE、固件(Firmware)、NVRAM
CFE的作用跟PC的BIOS一样是负责引导操作系统的;固件就是路由器的操作系统,就像PC上的Windows一样;NVRAM则用于存储路由器的设置,相当于PC的CMOS。
当路由器插上电后自动进入CFE,CFE进行类似PC的BIOS那样进行自检,自检通过后就引导路由器的固件了(相当于的PC的Windows了),正常情况下最多一分钟路由器的固件就会引导完毕,并且Power灯是常亮的。如果Power灯一闪一闪,那一般是CFE没有引导成功路由器的固件(固件不存在、不正确、已损坏等等)。
那么我们平常刷固件会把CFE也刷了吗?呵呵,不会的,就像你平时装Windows一样,不管你是装XP也好,Vista也好,Win7也好,都不会对你的BIOS进行更新。那我想刷CFE该用什么方法?一般用户用路由器原厂的CFE即可,如果要玩的深入一些想刷CFE,那么可以通过JTAG或者telnet方式来刷(比较危险,而且受固件限制,有些固件不支持)。
主要是对硬件环境进行初始化,image的更新,加载kernel等
posted @ 2013-11-13 20:34 Long Long Ago 阅读(229) | 评论 (0)编辑 收藏

好久没有更新这个blog了,java也放下了许久。现在开始重新更新本blog
posted @ 2010-10-30 13:44 Long Long Ago 阅读(215) | 评论 (0)编辑 收藏

本文主要介绍了JXTA中的各种概念。
1.Peer。一个peer就是实现了一个或多个JXTA协议的网络设备。比如传感器,电话, PDA,PC,服务器,巨型机等等。每一个peer与其他peer都是独立操作并且是异步的。有peer ID来唯一标识一个peer。peer使用JXTA协议公开一个或多个网络接口(network interface),每一个公开的接口都被广告为一个peer端点(peer endpoint),这个peer端点唯一标识了一个网络接口。peer之间不需要有直接的点对点的网络连接。可以使用中间的peer作为peer的消息路由,将由于硬件网络或者网络配置(NATs,防火墙或者代理)而造成的两个通信peer进行互联。peer通常被设计成网络中自然地互相发现,从而构成暂时的或持久的关系成为peer组(peer gorup)。
2.Peer Group.一个peer组是一个peer的集合,这些peer都有一组相同的服务。peer自组织的加入到peer组中,并通过一个唯一的peer组id来区别这些peer组。每一个peer组都可以建立一个属于自己的memebership policy,从任何人可以加入到最严格的安全验证以及受保护方式(需要完全的认证书来加入)。一个peer可以同时属于多于一个peer组。默认地,第一个peer组被实例的组是Net Peer Group。所有的peer都属于Net Peer Group。peer可以选择加入其他的peer group。JXTA协议描述了peer如何公开,发现,加入以及监控peer组。
     如下几点说明了创建peer组的目的。
  1. 建立安全的环境。peer组可以创建一个本地控制域,在这个域中使用一个特定的安全策略。这个安全策略可以简单的只是一个明文的帐号 /口令交换,也可以像PKI一样成熟。peer组界定认证的成员访问和公开受保护的内容(content)。peer组在建立了一个逻辑上的区域,从而对访问peer组的资源进行界定。
  2. 创建一个范围环境。peer组裕兴建立一个本地的专用域。比如,peer可以组织起来实现一个文档共享网络或者一个CPU共享网络。 peer组提供细分网络成抽象的区域来提供内在范围机制(implicit scoping mechanism)。比如,当搜索一个组内容的时,peer组可以界定定义一个搜索范围。
  3. 创建一个监视环境。peer组允许一个peer为了任何目的去监视一组peer(比如,心跳,traffic introspection或者accountability)。peer组也可以构建一个父子层次结构,其中任何一个组都有一个唯一的父亲。查询请求能偶在这个组中传播。对于这个组中的广告也可以在其父组中公开,当然也包裹这个组本身。
peer组提供了一组服务成为peer组服务。在JXTA中敌营了一个核心peer组服务集合。两个peer如果要通过一个服务进行交互,他们配需位于同一个peer中。
    核心的组服务有如下:
  1. 发现服务(Discovery Service)。本服务被组中的peer用来查询per组资源,比如peer,peer组,通道和服务等。
  2. 成员关系服务(Membership Service)。本服务被当前成员用来拒绝或接受一个新的组成员应用。一个peer想要加入到一个group前,首先要确定一个当前的成员,并请求加入。当前成员的集合可以拒绝或者接受某个想加入的应用(application)。本服务可能会发起一个所有peer或者指定组代表的一次投票来决定是否接受或者聚居新的成员应用。
  3. 访问服务(Access Service)。使用访问服务可以用来验证一个peer对另一个peer的请求(request)。如果访问被允许,那么接受请求的peer会提供给请求peer关于该请求所要知道的信息的信任和相关信息。(注意,在peer组中,不是所有的行为(action)都需要通过访问服务的检查,只有那些在某些peer中被限制的行为在需要调用时,才被检查。)
  4. 管道服务(Pipe Service)。本服务用于在peer组成员间的链接管道的建立和管理。
  5. Resolver服务。本服务用来发送一般的查询请求到其他的peer。peer可以定义和交换请求以便发现任何需要的信息(比如一个服务的状态或者一个管道端点的信息。)
  6. 监视服务(Monitoring Service)。本服务用来让一个peer监视本组中的其他成员服务。
    不是所有上面提到的服务都必须要被每个peer实现。一个peer组可以自由的实现那些它认为有用的服务,并可以依赖于默认的Net Peer Group来提供非关键核心服务的一般实现。
3.Network Services。peer之间可以协作并通信以发布,发现和启动网络服务。peer可以发布多个服务。peer通过Peer Discovery Protocol来发现network service。在JXTA协议中组织了两个层次上的network service。
  • Peer Service。一个peer service只有当peer公布了自己的service时才能被访问。如果这个peer失败了,那么它的service也失败了。不同的peer可以运行某个服务的多个实例,但每个实例都必须公开自己的广告(advertisement).
  • Peer Group Service。peer组服务是这个组中成员所运行服务的实例的集合的组织形式,其中这些实例是互相合作的。如果其中任何一个peer失败了,那么这个 peer组服务将不会收到影响(假定这个服务可以从其他peer获得)。peer group服务通过peer 组广告的以部分来发布。
    服务可以是事先被安装到peer上或者通过网络安装的。为了真正的运行一个服务,peer必须为定位一个适合当前peer运行环境的一个实现。这个从网络上查找,下载和安装一个服务的过程很类似于在Internet上搜索一个Web页面,取回这个页面然后再安装需要的插件的过程。
4.Modules。JXTA的module用来描述任何一段用来实现JXTA world中一个行为的“代码”的抽象表示(Abstraction)。Network Service就是在一个peer上实现的行为的一个最一般的例子。这个Module Abstraction不一定特指什么“代码”,它可以是一个Java类,也可以是Java jar,或者动态链接库dll,一个XML消息或是一个脚本。这个module的行为交给了module的实现者。对于一个实例(instance), module表示了一个网络服务(network service)在不同平台上的不同实现,这些平台比如说在java平台,MS windows平台,Solaris 平台。
    Module允许peer实现一个新的行为,通过提供了一个一般的抽象。当peer浏览或者加入一个peer组时,他可以查找新的其打算实现的行为。比如,当加入一个peer组后,一个peer可能必须学习新的搜索服务,这个服务只能在本peer组中使用。为了加入到这个组,这个peer必须实现这个新的搜索服务。这个module框架可以启动平台无关行为的表示(representation)和广告(advertisement)。并允许peer描述和实现任何形式这个行为(behavior)的是实现。比如,一个peer使用java或者c实现一个行为的实现。
& amp; nbsp;   描述和公告一个平台独立的行为的能力能有有效的支持peer组包含异构的peer。module的广告可以使JXTA的peer能够采用平台独立的方式描述一个行为。JXTA平台使用module广告来自描述。
    module抽象(Module abstractIon)包括一个module class,module specification和一个moduleimplementation。
  • Module Class。module class主要用于广告一个行为(behavior)的存在。这个class的定义表述了一个期望的行为和一个期望的对所支持的module的绑定。每一个module class都有一个唯一的ID,成为MoudleClassID
  • Module Specification。Module Specification主要用于访问这个module。它包含了访问或者启动这个module的所有必要的信息。比如,一个服务,他的module specification可能包含了一个用于和其他服务相通信的管道的广告。一个module specification用于提供module class所指明的功能。对于一个module class可能有多个module specification。每一个module specification都有一个唯一的id,ModuleSpecID。ModuleSpecID包含了ModuleClass ID,并指明了所使用的module class。一个module specification暗含了对网络的兼容性。对于一个给定的module specification的所有实现都必须使用相同的协议,这些实现都需要是兼容的,即使使用不同的语言实现的。
  • Module Implementation。Module Implement是一给定module specification的实现。对于一个module specification可以有多个module implementation。每一个module implementation都包含了它所实现的与specification相关联的ModuleSpecID。
    Module可以被peer组服务使用,也可以被独立的服务所使用。JXTA服务通过module abstraction来区别存在的服务(他的Module Class),服务的specification(Module Specification),或者服务的实现(Service Implementation)。所有的这些都有一个联合的广告,并且可以通过其他JXTA peer来公告和发现的。作为一个例子,考虑JXTA的发现服务。它包含一个唯一的ModuleClassID,标识了他作为一个发现服务——他的抽象功能。对于这个发现服务可以有多个不同的规范(Specification),并伴随着不同的实现。对于组的大小和在网络中的传播方式可以使用不同的裁剪策略。每一个Specification都有唯一的MdouleSpecID,其中指明了发现发现服务的ModuleClassID。对于每个规范,都可能有多种实现,每种实现都包含了相同的ModuleSpceID。
    总之,对于一个给定的module Class都可能有多种规范,这些规范可能是完全不同的。然而任何给定规范的所有实现都是假定可以互相兼容的。
posted @ 2007-05-19 23:11 Long Long Ago 阅读(927) | 评论 (0)编辑 收藏

JFace的单独使用很久都没有做过,基本上都是在开发elcipse插件时用JFace。今天使用JFace作为Java Application时遇到了 Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/core/runtime/IStatus 错误,虽然已经添加了org.eclipse.jface,org.eclipse.core.runtime,org.eclipse.osgi包,但仍然出错,后来发现org.eclipse.core.runtime.IStatus是在org.eclipse.equonix.common包中,添加后发现 ,还需要添加org.eclipse.core.command包,再添加后搞定。
posted @ 2007-05-17 14:33 Long Long Ago 阅读(1027) | 评论 (2)编辑 收藏

    以前实现SWT中的模式和非模式窗体(modal or non-modal window )是通过在子窗体中是否截取父窗体的消息循环来实现的,现在发现这样好像不行了,但可以通过指定子窗体的样式(style)来制定子窗体是否是模式窗体。
Shell child = new Shell(shell,SWT.SHELL_TRIM|SWT.APPLICATION_MODAL);
上面的语句指定了子窗体child为模式窗体。在上面的style中指定SWT.SHELL_TRIM 是为了显示窗体的三个默认按钮(最大,最小和关闭),也可以用SWT.DIALOG_TRIM,不过此时为对话框样式,只有一个默认按钮(关闭)。默认的Shell是非模式窗体,并且是有默认系统按钮的,即样式为:SWT.SHELL_TRIM|SWT.MODELESS。
顺便说一下,在JFace的Dialog中使用模式对话框只要简单的设置该对话框setBlockOnOpen(true)即可。
posted @ 2007-05-17 14:27 Long Long Ago 阅读(2087) | 评论 (0)编辑 收藏

    在JXTA中分为三个层次,如下图所示。

     下面分别介绍各个层次:
  1. 平台层(platform layer)。平台层即所谓的JXTA核心(JXTA core),专门包装了最小最精华的部分,这部分主要完成了对P2P网络最一般的使用。包括,为P2P应用程序的关键机制构建模块,传输(包括防火墙穿透),创建peer和peer group以及和安全部分的结合。
  2. 服务层(Service layer)。服务层包含了一些网络服务,这些服务不一定是P2P程序中必须的,但却是P2P环境中通常和值得提供的。比如搜索,索引,目录,存储系统,文件共享,分布式文件系统,资源聚合和租借等应用服务,以及协议传输服务和认证和PKI服务等等。
  3. 应用层(Application layer)。应用层包括了综合应用的实现,比如P2P的及时消息,文档和资源的共享,娱乐内容管理和分发,P2P的email系统,分布式拍卖系统以及很多其他的应用。
    实际上,服务层和应用层之间的界限并不是明显的。一个用户的应用程序可以作为另一个用户的服务。整个系统被实际成模块化的,允许开发者选择一个服务和应用的集合来定制自己的需求。
    在JXTA中有三个主要方面是它区别一其他分布式网络模型的:
  1. 使用XML文档(广告)来描述网络资源。
  2. 针对peer间和peer与端点(endpoint)间的抽象管道(abstraction pipe)不需要使用一个可信赖的中心名字/地址认真,比如DNS。
  3. 唯一的peer地址方案(peer IDs)。
在下面的部分将详细介绍JXTA的各个组成部分。
posted @ 2007-05-13 14:13 Long Long Ago 阅读(782) | 评论 (0)编辑 收藏

    JXTA是一个为P2P计算而开发设计的开发网路计算平台。它的目标是通过创建基本组件和服务来为peer group创造新的应用。JXTA是juxtpose的简称,指并列并排,这里是说P2P的方式和C/S方式以及B/S方式是同等地位了,都是传统的分布式计算模型。 JXTA为开发应用程序提供了一组开发协议集合和一个开放源码的参考实现。JXTA协议标准化了peer的风格:
  1. 互相发现;
  2. 在peer group中的自组织;
  3. 广告和发现网络服务;
  4. peer间的通讯;
  5. peer间的交互。
    JXTA协议被设计成独立于程序设计语言和独立于传输协议的。这些协议可以使用Java或者C/C++或者perl实现。同样,也能在TCP/IP,HTTP,Bluetooth或者其他传输协议上实现。JXTA协议使得开发者可以构建和部署P2P的服务或者应用程序,因为这些协议是独立于程序语言和传输协议,所以可以使得采用完全不同软件体系的异构终端设备能够交互通讯。使用JXTA技术,开发人员可以开发出基于网络的,能够交互的应用程序,这些应用程序有如下特点:
  1. 通过动态查询和防火墙穿越来发现本网络中的其他peer;
  2. 任何访问网络的节点都可以方便地共享文档;
  3. 在网络站点中查找minute content;
  4. 创建一个peer group来提供服务;
  5. 远端监视peer的行为;
  6. 在网络中同其他peer进行安全的通信。
posted @ 2007-05-12 23:59 Long Long Ago 阅读(835) | 评论 (0)编辑 收藏

今天打算试试yaws,这是一个使用erlang实现的web服务器,参照blog:
http://yarivsblog.com/articles/2006/07/12/the-hitchhiker
首先需要安装erlang环境,windows和linux下都有,但yaws好像只给了个linux下的安装文件,于是在ubuntu6.06上安装。
从erlang主页www.erlang.org上下载源码,编译步骤:
tar -xzvf xxx.tar.gz
cd xxx
sudo ./configure
(sudo make clean)#可选的,用于非首次编译的情况
sudo make
sudo make install (as root)
但configure时出错:(类似如下)
configure: error: No curseslibraryfunctions found
There is a problem with $ERL_TOP/erts/configure not passing the LDFLAGS
environment variable for test compiles, so ALL library tests fail. I
modified files $ERL_TOP/erts/aclocal.m4 and $ERL_TOP/configure.in in order
to locate pthread_create in the standard C runtime library. The
$ERL_TOP/configure.in produces a configure that works, wheras the
$ERL_TOP/erts/configure.in does not. At the top of file erts/configure.in:

AC_PREREQ(2.13)
AC_INIT(vsn.mk)
应该是一些curses库没有安装,但apt-get install curses提示找不到,google了一下发现需要安装
ncurses-devel包,在网上只找到了相应的rpm包,使用alien命令,将rpm保转换为deb包安装:
sudo apt-get install alien
sudo alien *.rpm,转成deb后,用dpkg -i *.deb
或者
sudo alien -i *.rpm
进行安装
(注意,下载rpm包的时候我将包改名了,导致alien时出错,改成原来的名字就正常了,不知道为什么:P)
这时候cofigure可以过去了,但make又出错了,提示erlc找不到,类似如下的错误信息:
erlc -W +debug_info -I../include -o../ebin otp_ring0.erl
make[4]: erlc: Command not found
make[4]: ***[../ebin/otp_ring0.beam]Error127
make[4]: Leaving directory `/usr/src/packages/erlang-10.b.5/lib/kernel/src'
make[3]: *** [/usr/src/packages/erlang-10.b.5/lib/kernel/ebin/otp_ring0.beam] Error 2
make[3]: Leaving directory `/usr/src/packages/erlang-10.b.5/erts/emulator'
make[2]: *** [generate] Error 2
make[2]: Leaving directory `/usr/src/packages/erlang-10.b.5/erts/emulator'
make[1]: *** [depend] Error 2
make[1]: Leaving directory `/usr/src/packages/erlang-10.b.5'
make: *** [build-stamp] Error 2
make: *** [debs] Error 2

看网上有介绍说使用make clean并不完全,
于是将源码删除,重新解压缩,再configure后,make,没有提示erlc找不到了,又有提示类似如下的错误信息:
> otp_src_R11B-1/lib/kernel/ebin/erlang.beam > i686-pc-linux-gnu/preload.c
> m4 -DTARGET=i686-pc-linux-gnu -DOPSYS=linux -DARCH=x86 hipe/
> hipe_x86_asm.m4 > i686-pc-linux-gnu/opt/plain/hipe_x86_asm.h
> /bin/sh: m4: command not found
> make[2]: *** [i686-pc-linux-gnu/opt/plain/hipe_x86_asm.h] Error 127
> make[2]: Leaving directory `/home/jhancock/otp_src_R11B-1/erts/emulator'
> make[1]: *** [generate] Error 2
> make[1]: Leaving directory `/home/jhancock/otp_src_R11B-1/erts/emulator'
> make: *** [depend] Error 2
这是m4包没有找到(http://www.erlang.org/pipermail/erlang-questions/2006-November/023942.html)
安装m4包后,继续make出现如下错误:
hipe/hipe_mkliterals.c:351: error: 'X86_LEAF_WORDS' undeclared here (not in a function)
hipe/hipe_mkliterals.c:352: error: 'X86_NR_ARG_REGS' undeclared here (not in a function)
网上搜索结果:http://forum.trapexit.org/viewtopic.php?t=6815
将源码删除,再解压缩 再make终于过去了
提示:如下的包最好是在erlang安装之前就安装好的: perl, debhelper (>= 4.0.0), autoconf (>= 2.50), openssl, libssl-dev, m4, libncurses5-dev, dpatch, autotools-dev, unixodbc-dev
make过程大概有1个小时,之后是make install很快。
打开终端,输入erl,出现提示符1>
后面将yaws的安装和使用
yaws的安装挺简单的,主要是他的编译是基于erlang的。
需要注意的是,安装好后,如果没有安装相应的ssl模块的话,需要更改一下conf文件,将其中关于ssl的部分注释掉,既如下部分:
<server xxx>
      ....
          <ssl>
                ...
          </ssl>
</server>
在windows下的安装,在yaws的主页上有说明,需要下载一个bat文件,不过这个文件好像有问题,需要将一些安装的环境变量改一下,在(SET ERLC_FLAGS=)这行之后添加:
SET ProgramFiles=G:\erlang\yaws\yaws_program
SET HOME=G:\erlang\yaws\yaws_data
SET APPDATA=G:\erlang\yaws\yaws_configure
ProgramFiles 为yaws的程序位置,HOME为yaws的数据位置,包括www,log,wiki,ssl等,APPDATA为yaws的配置位置。
如果使用默认安装参数的话,需要将IF NOT DEFINED ProgramFiles SET ProgramFiles="c:\Program Files"和
HOME="%HOMEDRIVE%%HOMEPATH%\My Documents"中的双引号去掉(感觉这个bat文件写的比较乱)
同时不要忘了,在安装好之后注释掉conf文件中关于ssl的部分。
安装好后,默认就可以在%home/www目录或者/tmp(windows下为%home%\www  or C:\tmp)目录添加yaws文件(ehtml)来显示了。
btw:如果在ubuntu or debian上安装,最简单,只要apt-get install yaws 就可以了。运行yaws需要root权限。
posted @ 2007-01-18 23:22 Long Long Ago 阅读(4444) | 评论 (2)编辑 收藏

今天看到一个朋友的Blog, 就忍不住把以前写的这个代码拿出来了, 不然这代码闲着也是闲着. 当然没有必要照搬全部, 只要中间的那个 zoomImage() 方法即可. 当然还有设置图片部分透明的方法.

 

/*
* @(#)BlogMailHandler.java 1.00 2004-10-4
*
* Copyright 2004 . All rights reserved.
* PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Timestamp;
import java.util.Properties;

import javax.imageio.ImageIO;
import javax.mail.Address;
import javax.mail.FetchProfile;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeUtility;

import studio.beansoft.jsp.StringUtil;
import com.keypoint.PngEncoder;
import com.keypoint.PngEncoderB;

import moblog.*;

/**
* BlogMailHandler, 彩E博客邮件的处理程序.
* 每 15 分钟更新一次邮件, 发布博客并更新用户产量.
* 邮件 POP3 帐户信息位于 /blogmail.properties 文件中.
*
* @author 刘长炯
* @version 1.00 2004-10-4
*/
public class BlogMailHandler extends Thread {
/**
* @alias 文件根目录 : String
*/
private String rootDirectory;

// 邮件帐户配置属性
private static Properties props = new Properties();

static {
try {
InputStream in = BlogMailHandler.class
.getResourceAsStream("/blogmail.properties");
props.load(in);
in.close();
} catch (Exception ex) {
System.err.println("无法加载配置文件 blogmail.properties:"
+ ex.getMessage());
ex.printStackTrace();
}
}

// 图像加载的共享实例, 在 Linux 平台上有可能无法生成图形对象
// private static Frame sharedFrame = new Frame();
private boolean shouldExit = false;

public BlogMailHandler() {
}
/** 定时开始读取邮件信息 */
public void startMailProcessCycle() {
start();
}
public void run() {
while(!shouldExit) {
doProcess();
try {
// 每15分钟读取一次
Thread.currentThread().sleep(60 * 15 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/** 处理进程 */
private void doProcess() {
try {
Store store = openStore();
Folder inbox = openInbox(store);

processAllMessages(inbox);
inbox.close(true);
store.close();
} catch (Exception e) {
e.printStackTrace();
}
}
protected void finalize() throws Throwable {
shouldExit = true;
}
/**
* 缩放原始图片到合适大小.
*
* @param srcImage - 原始图片
* @return BufferedImage - 处理结果
*/
private BufferedImage zoomImage(BufferedImage srcImage) {
int MAX_WIDTH = 100;// TODO: 缩放后的图片最大宽度
int MAX_HEIGHT = 160;// TODO: 缩放后的图片最大高度
int imageWidth = srcImage.getWidth(null);
int imageHeight = srcImage.getHeight(null);

// determine thumbnail size from MAX_WIDTH and MAX_HEIGHT
int thumbWidth = MAX_WIDTH;
int thumbHeight = MAX_HEIGHT;
double thumbRatio = (double)thumbWidth / (double)thumbHeight;
double imageRatio = (double)imageWidth / (double)imageHeight;
if (thumbRatio < imageRatio) {
thumbHeight = (int)(thumbWidth / imageRatio);
} else {
thumbWidth = (int)(thumbHeight * imageRatio);
}
// 如果图片小于所略图大小, 不作处理
if(imageWidth < MAX_WIDTH && imageHeight < MAX_HEIGHT) {
thumbWidth = imageWidth;
thumbHeight = imageHeight;
}

// draw original image to thumbnail image object and
// scale it to the new size on-the-fly (drawImage is quite powerful)
BufferedImage thumbImage = new BufferedImage(thumbWidth,
thumbHeight, BufferedImage.TYPE_INT_RGB);
//thumbImage.getsc
Graphics2D graphics2D = thumbImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.drawImage(srcImage, 0, 0, thumbWidth, thumbHeight, null);
System.out.println("thumbWidth=" + thumbWidth);
System.out.println("thumbHeight=" + thumbHeight);
return thumbImage;
}

// Open mail Store
private Store openStore() throws Exception {
Store store;
//--[ Set up the default parameters
props.put("mail.transport.protocol", "pop");
props.put("mail.pop.port", "110");
// props.put("mail.debug", "true");

Session session = Session.getInstance(props);
store = session.getStore("pop3");
// void javax.mail.Service.connect(String host, String user, String
// password) throws
// MessagingException
store.connect(props.getProperty("mail.pop3.host"), props
.getProperty("username"), props.getProperty("password"));
return store;
}

// Open Inbox
private Folder openInbox(Store store) throws Exception {
Folder folder = store.getDefaultFolder();
if (folder == null) {
System.out.println("Problem occurred");
System.exit(1);
}

Folder popFolder = folder.getFolder("INBOX");
popFolder.open(Folder.READ_WRITE);
return popFolder;
}

/** Close mail store. */
private void closeStore(Store store) {
try {
store.close();
} catch (MessagingException e) {
e.printStackTrace();
}
}

/**
* 处理账号中的所有邮件并删除这些邮件.
*
* @param folder - Folder, 收件箱
* @throws Exception
*/
private void processAllMessages(Folder folder) throws Exception {

Message[] listOfMessages = folder.getMessages();
FetchProfile fProfile = new FetchProfile();
fProfile.add(FetchProfile.Item.ENVELOPE);
folder.fetch(listOfMessages, fProfile);

for (int i = 0; i < listOfMessages.length; i++) {
try {
processSingleMail(listOfMessages[i]);
} catch (Exception e) {
e.printStackTrace();
}

// Delete mail
listOfMessages[i].setFlag(Flags.Flag.DELETED, true);
}
}

/**
* 处理单个 Email, 将文章发表, 并将附件图片转换为 PNG 后存入用户目录.
*
* @param message -
* Message, email 消息
*/
private void processSingleMail(Message message) throws Exception {
BlogContent content = new BlogContent();
BlogUser user = new BlogUser();
BlogPicture picture = new BlogPicture();

// 1. 假定发件人为手机号, 并尝试根据手机号查找用户
Address[] addList = message.getFrom();
if (addList.length > 0) {
String userMail = ((InternetAddress) addList[0]).getAddress();
// 取出 彩E 邮件用户手机号, 格式: 手机号@someone.com
String mobileNumber = userMail.substring(0, userMail.indexOf("@"));
System.out.println("用户手机号为:" + mobileNumber);
if (!user.findByMDN(mobileNumber)) {
// Not found user, then return
System.err.println("user " + ((InternetAddress) addList[0]).getAddress() + " not found.");
return;
}
}

// 2. 尝试读取邮件正文
// 复合邮件
if (message.isMimeType("multipart/*")) {
// 标记是否处理过图片
boolean imageHasProcessed = false;
Multipart multipart = (Multipart) message.getContent();
for (int i = 0, n = multipart.getCount(); i < n; i++) {
// System.err.println("Reading multipart " + i);
Part part = multipart.getBodyPart(i);
// System.err.println("ContentType = " + part.getContentType());

// 3. 处理附件图片, 只处理第一个图片
String disposition = part.getDisposition();
// System.err.println("disposition = " + disposition);
if (disposition != null
&& (disposition.equals(Part.ATTACHMENT) || disposition
.equals(Part.INLINE)) && !imageHasProcessed) {
// 需要反编码邮件文件名, 有的邮件的附件的文件名是经过编码的,
// 但是 JavaMail 并不能处理出来(BeanSoft, 2004-10-13)
String fileName = MimeUtility.decodeText(part.getFileName());
String ext = StringUtil.getExtension(fileName)
.toLowerCase();
System.err.println("part.getFileName() = " + fileName);

if ("gif".equals(ext) || "jpg".equals(ext)
|| "png".equals(ext)) {
BufferedInputStream dataIn = null;
// 转换非 PNG 格式图片为 PNG 格式 -- 取消
// if (!"png".equals(ext)) {
ByteArrayOutputStream pngOut = new ByteArrayOutputStream();
try {
// Convert image file to PNG file
BufferedImage buffImg = ImageIO.read(part
.getInputStream());
// Read image file from attachment
// 缩放图片
buffImg = zoomImage(buffImg);

int imageWidth = buffImg.getWidth(null);
int imageHeight = buffImg.getHeight(null);
BufferedImage outImg = new BufferedImage(
imageWidth, imageHeight,
// BufferedImage.TYPE_4BYTE_ABGR 是 24 位色深, TYPE_BYTE_INDEXED 是 8 位
BufferedImage.TYPE_INT_RGB);
// 使图片透明
// embossImage(buffImg, outImg);
outImg.getGraphics().drawImage(buffImg, 0, 0, imageWidth, imageHeight, null);
// Save image to PNG output stream
// ImageIO.write(outImg, "png", pngOut);
// Save using keypoint PNG encoder
PngEncoderB pngb = new PngEncoderB(outImg,
PngEncoder.NO_ALPHA, 0, 9);
pngOut.write(pngb.pngEncode());
dataIn = new BufferedInputStream(
new ByteArrayInputStream(pngOut
.toByteArray()));
} catch (Exception e) {
}
// } else {
// dataIn = new BufferedInputStream(part
// .getInputStream());
// }
// All pictures change to png format
ext = "png"
// Insert picture info into database
picture.setBlogID(user.getId());
picture.setCreationTime(new Timestamp(System
.currentTimeMillis()));
picture.setFileEXName(ext);
picture.setTitle(fileName);
picture.create();
// Save png file to user directory, /users/userId/pictureId.png
FileOutputStream outFile = new FileOutputStream(
rootDirectory + File.separatorChar + user.getId() + File.separatorChar
+ picture.getId() + "." + ext);
int c;
while ((c = dataIn.read()) != -1) {
outFile.write(c);
}

outFile.close();
imageHasProcessed = true;
}
}
// 纯文本邮件, 带附件
else if (part.isMimeType("text/plain")) {
String body = part.getContent().toString();
String title = message.getSubject();

content.setBlogID(user.getId());
content.setCreationTime(new Timestamp(System.currentTimeMillis()));
content.setTitle(title);
content.setNote(body);
}

// 典型的 HTML 和 文本邮件可选形式, 进一步分析
if (part.getContent() instanceof Multipart) {
Multipart subPart = (Multipart) part.getContent();

for (int j = 0, m = subPart.getCount(); j < m; j++) {
Part mailText = subPart.getBodyPart(j);

if (mailText.isMimeType("text/plain")) {
String body = mailText.getContent().toString();
String title = message.getSubject();

content.setBlogID(user.getId());
content.setCreationTime(new Timestamp(System.currentTimeMillis()));
content.setTitle(title);
content.setNote(body);
break;
}
}
}

}// End of multipart parse

// 4. 创建博客记录
content.setPictureId(picture.getId());
if(content.create() > 0) {
// 更新用户产量
user.setPostTimes(user.getPostTimes() + 1);
user.update();
}
}
// 纯文本邮件, 无附件
else if (message.isMimeType("text/plain")) {
String body = message.getContent().toString();
String title = message.getSubject();

content.setBlogID(user.getId());
content.setCreationTime(new Timestamp(System.currentTimeMillis()));
content.setTitle(title);
content.setNote(body);

if(content.create() > 0) {
// 更新用户产量
user.setPostTimes(user.getPostTimes() + 1);
user.update();
}
}
}

// 测试, 尝试连接一次到邮件服务器
public static void main(String[] args) {
BlogMailHandler handler = new BlogMailHandler();

// TODO: debug, 请在 JSP 里面设置图片目录的根路径
handler.rootDirectory = "F:/Moblog/users/"
handler.doProcess();
}

/**
* @return Returns the rootDirectory.
*/
public String getRootDirectory() {
return rootDirectory;
}

/**
* @param rootDirectory
* The rootDirectory to set.
*/
public void setRootDirectory(String property1) {
this.rootDirectory = property1;
}

/** Make image transparent */
private void embossImage(BufferedImage srcImage, BufferedImage destImage) {
int width = srcImage.getWidth();
int height = srcImage.getHeight();

for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int newColor = handlesinglepixel(j, i, srcImage.getRGB(j, i));
destImage.setRGB(j, i, newColor);
}
}
}

// Handle picture single pixel, change 0xff00ff color to transparent
private int handlesinglepixel(int x, int y, int pixel) {
int alpha = (pixel >> 24) & 0xff;
int red = (pixel >> 16) & 0xff;
int green = (pixel >> 8) & 0xff;
int blue = (pixel) & 0xff;
// Deal with the pixel as necessary...
// alpha 为 0 时完全透明, 为 255 时不透明
Color back = new Color(0xFF00FF);
// 将与背景色相同(此处PNG图片为紫色)的像素点的透明度设为透明
if (isDeltaInRange(back.getRed(), red, 2)
&& isDeltaInRange(back.getGreen(), green, 2)
&& isDeltaInRange(back.getBlue(), blue, 2)) {
// System.out.println("x=" + x + "y=" + y + " is transparent.");
alpha = 0;
}
// red = red / 2;
// //green = green / 2 + 68;
// blue = blue / 2;

return alpha << 24 | red << 16 | green << 8 | blue;
}

// 判断两个整数的差是否在一定范围之内
private boolean isDeltaInRange(int first, int second, int range) {
if (first - second <= range && second - first <= range)
return true;
return false;
}
}

  • # re: Java 中收取邮件并自动缩放图片的代码(原创)
    冷面阎罗
    Posted @ 2006-12-29 18:31
    不错!
    自己也写过java收发邮件的程序!  回复  
  • # re: Java 中收取邮件并自动缩放图片的代码(原创)
    托托姆
    Posted @ 2006-12-30 12:10
    不知道BeanSoft兄是不是看了我昨天的帖子有此感想。。。:)
    我测试了一下BeanSoft兄的zoomImage() 方法,如果按原代码执行,原本图片透明的部分将变成黑色。如果修改TYPE_INT_RGB为TYPE_INT_ARGB,则能避免这个问题。
posted @ 2006-12-30 13:27 Long Long Ago 阅读(569) | 评论 (0)编辑 收藏

写程序中遇到一个问题 如下:
mySoc = new Socket(svrAddress,5555);


myInput = new ObjectInputStream(mySoc.getInputStream());//有问题
myOutput = new ObjectOutputStream(mySoc.getOutputStream());//有问题
//myInput = new DataInputStream(mySoc.getInputStream());
//myOutput = new DataOutputStream(mySoc.getOutputStream()); 
注销的语句运行可以成功
但是未注销的那部分 运行时就卡在那里了
但是却没有抛出异常
请教原因是什么 有什么问题
该怎么解决呢?

找了好久终于再网上找到关于这个问题的说明了 因为问题比较特殊 所以贴出来希望对大家
有帮助

主机端先建立ObjectInputStream后建立ObjectOutputStream,则对应地客户端要先建立
ObjectOutputStream后建立ObjectInputStream,否则会造成两方互相等待数据而导致死
锁。

原因是建立ObjectInputStream对象是需要先接收一定的header数据,接收到这些数据之前
会处于阻塞状态。故而为了防止这种死锁状态,通讯两方的
ObjectInputStraem,ObjectOutputStream必须注意顺序对应使用。


目前相应的解决办法还没有找到 如果要解决 可以尝试重载对象输入输出流
posted @ 2006-12-30 12:46 Long Long Ago 阅读(927) | 评论 (1)编辑 收藏

       Tabbed Property是eclipse3.2中新加入一个view,可以使属性编辑器的功能近乎无限的扩大。这里说明一些Tabbed Property的使用方法。Tabbed Property中分成三个部分,Contributer,Tabs,Sections,一个Contributor包含若干个Tabs,一个Tabs又可以包含若干个sections。下面我们来分别进行描述。
      1。Contributor 这需要扩展org.eclipse.ui.views.properties.tabbed.PropertyContributor扩展点,定义时,最重要的是定义contributId,这个id必须是全局唯一的,这样在加载属性页时,才能找到这个我们定义的属性页,一般地,我们都将对应于这个属性页的workbenchpart的id作为本contributor的id,这样我们在代码中可以不硬编码本id字符串,而使用getSite().getId()就可以得到这个id了(当然,这样定义id不是必须的)。一个property view可以被多个workbench part共享,但 一个workbench part只能有一个property view,这个workbench part需要实现ITabbedPropertySheetPageContributor 接口,这个接口只有一个方法,要求返回本part对应的tabbed property Contributor id,一般只要return getSite().getId();
   contributor有如下几个attribute:
   1)typeMapper,这个类需要实现org.eclipse.ui.views.properties.tabbed.ITypeMapper,主要是实现类型的映射,因为我们选择的元素并不一定是实现IPropertySource的元素(即能够给property view提供内容的元素),比如在GEF中,我们选择的finger实际上是选择了对应的EditPart,而实际上实现了IPropertySource一般的是model部分的元素,所以这时候我们要将Editpart映射到对应的model元素。
   2)labelProvider,需要一个实现org.eclipse.jface.viewers.ILabelProvider的类,主要是在各个tabs的最上面显示文字和图片。
   3)propertyCategory,用于聚合多个tabs,注意至少要定义一个category,来聚合tabs,否则,可能会显示property失败。

   2。Tabs,这个需要扩展org.eclipse.ui.views.properties.tabbed.propertyTabs扩展点,其中contributorId就是与之相关联的Contributor的id,然后我们可以定义多个tab,这些tab的属性如下:
   1)label,用于显示在property view的tab bar上的字
   2)category,填入的就是在Contributor扩展点中定义的那些category,用于聚合tabs
   3)id,本tab的唯一标识
   4)afterTab,用于tab之间的排序,如果这是第一个tab,则没有afterTab,afterTab指的是在本tab之前的那个tab,并且afterTab描述的是在同一个category中的tabs,不同category之间的顺序是按照在contributor中定义category的顺序来定义的。
   5)indented,如果为ture,则各个tabs是有缩进的
   6)image,本tab的图片

   3。section ,需要扩展 org.eclipse.ui.views.properties.tabbed.PropertySections扩展点,它的contributionId就是本section所在的Contribution的id,针对每个tab,我们可以定义多个section,每个section的attribut描述如下:
   1)id,本secation的唯一标识
   2)tab,本section所属tab的标识
   3)class,实现了org.eclipse.ui.views.properties.tabbed.AbstractPropertySection抽象类的类,用于描述这个section的控件和布局。
   4)aftersection和上面的aftertab差不多,描述的是同一个tab中的section的顺序,注意afterserction描述的是本section之前的section的id
   5)filter:一个实现org.eclipse.jface.viewers.IFilter接口的过滤器,对选中元素进行过滤。
   6)enableFor:一个用于只是选择数目的值,必须要符合这个舒服才能使能这个section。如果不符合,则这个section就被过滤了,如果省略本值,则section的使能器就不会工作了。这是一个自然数,比如,当enableFor=1时,仅仅只有一个元素被选择的时候,本section才会被使能。

some notes:
    上面说过实现ITabbedPropertySheetPageContributor接口的workbench part除了要实现getContributeId方法外,还需要重载getAdapter方法,因为eclipse的默认加载的property veiw时原来的那个view,为了使tabbed property view能够加载,我们就需要重载getAdapter方法,返回一个TabbedPropertySheetPage对象。

    在实现section class的时候需要注意,createcontrol时首先应该先创建一个composite,一般是 Composite composite = getWidgetFactory().createFlatFormComposite(parent); 然后各个控件在这个composite上创建。


posted @ 2006-09-17 22:24 Long Long Ago 阅读(2852) | 评论 (1)编辑 收藏

安装subversion
基本命令:
  $ sudo apt-get install subversion
  $ sudo apt-get install libapache2-svn
可以安装的包:
 apache2
 apache2-common
 apache2-mpm-prefork
 apache2-utils
 libapache2-svn
 libapache2-mod-auth-pam
 libapache2-mod-auth-sys-group
 subversion
 subversion-tools


创建一个名为subversion的组:groupadd subversion
将自己(eg.:user)和www-data(apapch2帐号)用户添加入subversion组,可以编辑/etc/group文件,在最后找到subversion添加入帐号名(eg:user,www-data),看上去就像这样:subversion:x:1001:www-data,exp
然后是创建subversion库,并赋予subversion组中用户有读写subversion库的权限:
   $ sudo mkdir /home/svn  #创建svn库的父路径
   $ cd /home/svn
   $ sudo mkdir myproject  #创建本svn库的目录
   $ sudo svnadmin create /home/svn/myproject #使用svn命令,创建svn库
   $ sudo chown -R root:subversion myproject #更改本目录的组
   $ sudo chmod -R g+rws myproject #给本目录的组用户增加读写和递归增加新加目录的读写权限
注意上面提到的命令顺序,如果最后再执行创建库的命令(svnadmin create ....)则创建的文件没有获得组用户写的权限,这样在外部访问提交的时候会出错.
对于本机,可以直接使用file命令来访问:
  $ svn co(or checkout) file:///home/svn/myproject
#or
  $ svn co file://localhost/home/svn/myproject
注意:如果您并不确定主机的名称,您必须使用三个斜杠(///),而如果您指定了主机的名称,则您必须使用两个斜杠(//).
此时对svn库的权限是基于文件系统的,只要是subversion组中的用户都可以访问本svn库。

接下来,讲述如何使用apache服务器来提供对svn库的访问
编辑文件/etc/apache2/mods-available/dav_svn.conf
增加如下的内容:
  <Location /svn/myproject>
     DAV svn
     SVNPath /home/svn/myproject
     AuthType Basic
     AuthName "myproject subversion repository"
     AuthUserFile /etc/subversion/passwd
     
<LimitExcept GET PROPFIND OPTIONS REPORT>
        Require valid-user
     
</LimitExcept>
  
</Location>

apache会解析url中的/svn/myproject部分,来定位svn库,当收到此请求时,会查询svn库:/home/svn/myproject,这里的认证方式是basic,对于访问要求valid-user,帐号文件在/etc/subversion/passwd中。
注意重新设置后要重启apache2:sudo /etc/init.d/apache2 restart
编辑生成帐号文件: sudo htpasswd2 -c /etc/subversion/passwd user  #给user帐号创建口令
这时候可以通过浏览器来浏览svn库了
在我的设置中发现,apache2会自动绑定ipv6地址,可能会有些问题,可以强制apache绑定v4地址,在/etc/apache2/port.conf中改成:Listen [bindedip]:[port]的形式

通过https来访问svn库
首先生成一个 SSL 签名,使用命令

 # apache2-ssl-certificate

这里会有一系列关于你的个人隐私的问题,回答完了,自然的签名也就生成了,然
后我们就要在 apache2 里面打开 SSL 了,现在要做的是开启 ssl 模块

 # a2enmod ssl

然后,使用 apache2 的虚拟主机功能来添加 SSL 的支持,将

 /etc/apache2/sites-available/default

复制一份,叫

 /etc/apache2/sites-available/ssl

好啦

修改 default 文件的开头为

 NameVirtualHost *:80
 <VirtualHost *:80>

修改 ssl 文件的开头为

 NameVirtualHost *:443
 <VirtualHost *:443>

这里 443 是 SSL 的标准端口。

并在 ssl 文件中加入如下内容,在<VirtualHost></VirtualHost>内

 SSLEngine On
 SSLCertificateFile /etc/apache2/ssl/apache.pem

保存文件后,运行命令

   # a2ensite ssl

来激活这个虚拟主机

现在,修改文件

 /etc/apache2/ports.conf

加上一行

 Listen 443

好了,到此为止,SSL 服务器配置完成,重新启动 apache 吧。

 

一些问题:
可能出现 RA layer request failed svn: MKACTIVITY of 400 Bad Request 之类的错误,这可能是因为使用了代理的原因,代理不支持svn的扩展命令,see:http://subversion.tigris.org/faq.html#proxy
还有种原因,就是可能是你的客户端使用的是windowsxp,其他版本的windows我没试过,也是这样的错误,在linux下正常,解决方法不太清楚。
RA layer request failed svn: MKACTIVITY of 400 Bad Request,无论什么原因都可以用https代替http来暂时解决这样的问题。

参考:
http://fanqiang.chinaunix.net/app/web/2005-05-18/3257.shtml
http://wiki.ubuntu.org.cn/SubVersion?highlight=%28subversion%29

posted @ 2006-09-05 17:00 Long Long Ago 阅读(2948) | 评论 (0)编辑 收藏

在sources.list中添加如下几个源:
deb http://www.beerorkid.com/compiz/ dapper main
deb http://xgl.compiz.info/ dapper main
deb-src http://xgl.compiz.info/ dapper main
添加代理:
export http_proxy="http://xxx.xxx.xxx.xxx:xxxx"
获取pgp密钥:
wget http://www.beerorkid.com/compiz/quinn.key.asc -O - | sudo apt-key add - 

nivida的驱动:
sudo apt-get install nvidia-kernel-common nvidia-glx
编辑文件:/etc/X11/xorg.conf
在module部分中确定lode xgl,有如下代码:
Load "glx"
在devices部分修改除了Identifier行的其他各行,修改后如下:
Section "Device"
    Identifier- leave this line alone!
    Driver        "nvidia"
    BusID        "PCI:1:0:0"
    Option         "RenderAccel"         "true"
EndSection
在最下面添加Extensions部分,代码如下:
Section "Extensions"
          Option  "Composite" "Enable"
EndSection
下面是安装必要的库文件:
sudo apt-get install compiz xserver-xgl libgl1-mesa xserver-xorg libglitz-glx1 compiz-gnome
以上是引文http://www.ubuntuforums.org/showthread.php?t=131267 中的方法,此文所讲的后面是加载方法,我没有采用,用的是这里讲的方法:http://forum.ubuntu.org.cn/viewtopic.php?t=16777 不过这里讲的安装方法中少了一个库文件,呵呵
设置xgl启动入口:
新建一个xgl启动脚本/usr/bin/startxgl.sh,内容如下:
Xgl -fullscreen :1 -ac -accel glx:pbuffer -accel xv:pbuffer & sleep 2 && DISPLAY=:1
# Start GNOME
exec gnome-session 
使脚本可执行: sudo chmod 755 /usr/bin/startxgl.sh
新建一个compiz脚本/usr/bin/startcompiz,内容如下:
#!/bin/sh
killall gnome-window-decorator
wait
gnome-window-decorator & LD_PRELOAD=/usr/lib/fglrx/libGL.so.1.2.xlibmesa
compiz --replace gconf miniwin decoration transset wobbly fade minimize cube rotate zoom scale move resize place switcher trailfocus water & 
使得脚本可执行:sudo chmod 755 /usr/bin/startcompiz
在登陆管理器里建一个XGL会话: 建立一个文件/usr/share/xsessions/xgl.desktop ,内容如下:
[Desktop Entry]
Encoding=UTF-8
Name=XGl
Exec=/usr/bin/startxgl.sh
Icon=
Type=Application 

打开桌面菜单-〉系统-〉首选项-〉会话
在最右边的“启动程序”里添加 /usr/bin/startcompiz 这句话
最后不要忘了

sudo aptitude update
sudo aptitude upgrade
关闭所有程序
ctrl-alt-backspace启动X
登录时在会话中选择xgl
会提示是否为默认会话,建议选择仅本次
哦,差点忘了,怎么使用:
CTRL + ALT + Left/right arrow key. Switches to the new side of the cube for me.

CTRL + ALT + SHIFT + Left/Right arrow key- Takes the in focused app around cube.

CTRL + ALT + Left Click on Desktop - allows you to use the mouse to rotate cube.

F12 - uses the Expose like trick

Alt- Tab - switcher Vista-style
看起来有点晕,尤其是输入法的浮动窗体
posted @ 2006-08-31 13:03 Long Long Ago 阅读(888) | 评论 (3)编辑 收藏

java.lang.NoClassDefFoundError: com/sun/tools/javac/Main
最近在使用java的动态编译的时候出现的问题,主要是由于在使用类com.sun.tool.javac.Main时,总是出现NoClassDefFoundError的错误,后来找到如下的文章,分析,可能是由于对于包tools.jar的加载问题,虽然我在classpath中声明了这个包,但在eclipse环境下,始终都还是出现运行时异常,对于编译时正确,运行时异常的情况,eclipse一般都是由于其自身的加载机制造成的.在eclipse下,对于一般的java工程,只要设置了系统的classpath,在其中添加了tools.jar包,即可;对于plugin工程,我是将tools.jar包,直接拷贝到本工程下,并在property中引用,而且在META-INF/MANIFEST.MF文件中的Runtime页的classpath中添加了这个tool.jar包,这样在运行时就没有异常了,可以正常编译了.

理解Java ClassLoader机制

Java ClassLoader

2006-5-23
当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:

       bootstrap classloader
                |
       extension classloader
                |
       system classloader

bootstrap classloader -引导(也称为原始)类加载器,它负责加载Java的核心类。在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或使用- D选项指定sun.boot.class.path系统属性值可以指定附加的类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。大家可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:
   URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
   for (int i = 0; i < urls.length; i++) {
     System.out.println(urls.toExternalform());
   }
在我的计算机上的结果为:
文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/dom.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/sax.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xalan-2.3.1.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xercesImpl-2.0.0.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xml-apis.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xsltc.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/rt.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/i18n.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/sunrsasign.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/jsse.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/jce.jar
文件:/C:/j2sdk1.4.1_01/jre/lib/charsets.jar
文件:/C:/j2sdk1.4.1_01/jre/classes
这时大家知道了为什么我们不需要在系统属性CLASSPATH中指定这些类库了吧,因为JVM在启动的时候就自动加载它们了。

extension classloader -扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。在这个实例上调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。所以当大家执行以下代码时:
   System.out.println(System.getProperty("java.ext.dirs"));
   ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();
   System.out.println("the parent of extension classloader : "+extensionClassloader.getParent());
结果为:
C:\j2sdk1.4.1_01\jre\lib\ext
the parent of extension classloader : null
extension classloader是system classloader的parent,而bootstrap classloader是extension classloader的parent,但它不是一个实际的classloader,所以为null。

system classloader -系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。总能通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。执行以下代码即可获得:
   System.out.println(System.getProperty("java.class.path"));
输出结果则为用户在系统属性里面设置的CLASSPATH。
classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。


每个ClassLoader加载Class的过程是:
1.检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2
2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
3.请求parent classloader载入,如果成功到8,不成功到5
4.请求jvm从bootstrap classloader中载入,如果成功到8
5.寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
6.从文件中载入Class,到8.
7.抛出ClassNotFoundException.
8.返回Class.

其中5.6步我们可以通过覆盖ClassLoader的findClass方法来实现自己的载入策略。甚至覆盖loadClass方法来实现自己的载入过程。

类加载器的顺序是:
先是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类,也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。大家可以执行一下以下的代码:
   System.out.println(System.class.getClassLoader());
将会看到结果是null,这就表明java.lang.System是由bootstrap classloader加载的,因为bootstrap classloader不是一个真正的ClassLoader实例,而是由JVM实现的,正如前面已经说过的。

下面就让我们来看看JVM是如何来为我们来建立类加载器的结构的:
sun.misc.Launcher,顾名思义,当你执行java命令的时候,JVM会先使用bootstrap classloader载入并初始化一个Launcher,执行下来代码:
  System.out.println("the Launcher's classloader is "+sun.misc.Launcher.getLauncher().getClass().getClassLoader());
结果为:
  the Launcher's classloader is null (因为是用bootstrap classloader加载,所以class loader为null)
Launcher 会根据系统和命令设定初始化好class loader结构,JVM就用它来获得extension classloader和system classloader,并载入所有的需要载入的Class,最后执行java命令指定的带有静态的main方法的Class。extension classloader实际上是sun.misc.Launcher$ExtClassLoader类的一个实例,system classloader实际上是sun.misc.Launcher$AppClassLoader类的一个实例。并且都是 java.net.URLClassLoader的子类。

让我们来看看Launcher初试化的过程的部分代码。

Launcher的部分代码:
public class Launcher  {
   public Launcher() {
       ExtClassLoader extclassloader;
       try {
           //初始化extension classloader
           extclassloader = ExtClassLoader.getExtClassLoader();
       } catch(IOException ioexception) {
           throw new InternalError("Could not create extension class loader");
       }
       try {
           //初始化system classloader,parent是extension classloader
           loader = AppClassLoader.getAppClassLoader(extclassloader);
       } catch(IOException ioexception1) {
           throw new InternalError("Could not create application class loader");
       }
       //将system classloader设置成当前线程的context classloader(将在后面加以介绍)
       Thread.currentThread().setContextClassLoader(loader);
       ......
   }
   public ClassLoader getClassLoader() {
       //返回system classloader
       return loader;
   }
}

extension classloader的部分代码:
static class Launcher$ExtClassLoader extends URLClassLoader {

   public static Launcher$ExtClassLoader getExtClassLoader()
       throws IOException
   {
       File afile[] = getExtDirs();
       return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile));
   }
  private static File[] getExtDirs() {
       //获得系统属性“java.ext.dirs”
       String s = System.getProperty("java.ext.dirs");
       File afile[];
       if(s != null) {
           StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
           int i = stringtokenizer.countTokens();
           afile = new File;
           for(int j = 0; j < i; j++)
               afile[j] = new File(stringtokenizer.nextToken());

       } else {
           afile = new File[0];
       }
       return afile;
   }
}

system classloader的部分代码:
static class Launcher$AppClassLoader extends URLClassLoader
{

   public static ClassLoader getAppClassLoader(ClassLoader classloader)
       throws IOException
   {
       //获得系统属性“java.class.path”
       String s = System.getProperty("java.class.path");
       File afile[] = s != null ? Launcher.access$200(s) : new File[0];
       return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader));
   }
}

看了源代码大家就清楚了吧,extension classloader是使用系统属性“java.ext.dirs”设置类搜索路径的,并且没有parent。system classloader是使用系统属性“java.class.path”设置类搜索路径的,并且有一个parent classloader。Launcher初始化extension classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。

  这里怎么又出来一个context classloader呢?它有什么用呢?我们在建立一个线程Thread的时候,可以为这个线程通过setContextClassLoader方法来指定一个合适的classloader作为这个线程的context classloader,当此线程运行的时候,我们可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入我们所需要的Class。默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,父classloader可以获得当前线程的context classloader,而这个context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以从其获得所需的 Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当我们的classpath是在运行时才确定,并由定制的 classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例如web应用中的servlet就是用这种机制加载的.


好了,现在我们了解了classloader的结构和工作原理,那么我们如何实现在运行时的动态载入和更新呢?只要我们能够动态改变类搜索路径和清除classloader的cache中已经载入的Class就行了,有两个方案,一是我们继承一个classloader,覆盖loadclass方法,动态的寻找Class文件并使用defineClass方法来;另一个则非常简单实用,只要重新使用一个新的类搜索路径来new一个classloader就行了,这样即更新了类搜索路径以便来载入新的Class,也重新生成了一个空白的cache(当然,类搜索路径不一定必须更改)。噢,太好了,我们几乎不用做什么工作,java.netURLClassLoader正是一个符合我们要求的classloader!我们可以直接使用或者继承它就可以了!

这是j2se1.4 API的doc中URLClassLoader的两个构造器的描述:
URLClassLoader(URL[] urls)
         Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader.
URLClassLoader(URL[] urls, ClassLoader parent)
         Constructs a new URLClassLoader for the given URLs.
其中URL[] urls就是我们要设置的类搜索路径,parent就是这个classloader的parent classloader,默认的是system classloader。


好,现在我们能够动态的载入Class了,这样我们就可以利用newInstance方法来获得一个Object。但我们如何将此Object造型呢?可以将此Object造型成它本身的Class吗?

首先让我们来分析一下java源文件的编译,运行吧!javac命令是调用“JAVA_HOME/lib/tools.jar”中的“com.sun.tools.javac.Main”的compile方法来编译:

   public static int compile(String as[]);

   public static int compile(String as[], PrintWriter printwriter);

返回0表示编译成功,字符串数组as则是我们用javac命令编译时的参数,以空格划分。例如:
javac -classpath c:\foo\bar.jar;. -d c:\ c:\Some.java
则字符串数组as为{"-classpath","c:\\foo\\bar.jar;.","-d","c:\\","c:\\Some.java"},如果带有PrintWriter参数,则会把编译信息出到这个指定的printWriter中。默认的输出是System.err。

其中 Main是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,编译器在解析这个java源文件时所发现的它所依赖和引用的所有Class也将由system classloader载入,如果system classloader不能载入某个Class时,编译器将抛出一个“cannot resolve symbol”错误。

所以首先编译就通不过,也就是编译器无法编译一个引用了不在CLASSPATH中的未知Class的java源文件,而由于拼写错误或者没有把所需类库放到CLASSPATH中,大家一定经常看到这个“cannot resolve symbol”这个编译错误吧!

其次,就是我们把这个Class放到编译路径中,成功的进行了编译,然后在运行的时候不把它放入到CLASSPATH中而利用我们自己的 classloader来动态载入这个Class,这时候也会出现“java.lang.NoClassDefFoundError”的违例,为什么呢?

我们再来分析一下,首先调用这个造型语句的可执行的Class一定是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,当我们进行造型的时候,JVM也会使用system classloader来尝试载入这个Class来对实例进行造型,自然在system classloader寻找不到这个Class时就会抛出“java.lang.NoClassDefFoundError”的违例。

OK,现在让我们来总结一下,java文件的编译和Class的载入执行,都是使用Launcher初始化的system classloader作为类载入器的,我们无法动态的改变system classloader,更无法让JVM使用我们自己的classloader来替换system classloader,根据全盘负责原则,就限制了编译和运行时,我们无法直接显式的使用一个system classloader寻找不到的Class,即我们只能使用Java核心类库,扩展类库和CLASSPATH中的类库中的Class。

还不死心!再尝试一下这种情况,我们把这个Class也放入到CLASSPATH中,让system classloader能够识别和载入。然后我们通过自己的classloader来从指定的class文件中载入这个Class(不能够委托 parent载入,因为这样会被system classloader从CLASSPATH中将其载入),然后实例化一个Object,并造型成这个Class,这样JVM也识别这个Class(因为 system classloader能够定位和载入这个Class从CLASSPATH中),载入的也不是CLASSPATH中的这个Class,而是从 CLASSPATH外动态载入的,这样总行了吧!十分不幸的是,这时会出现“java.lang.ClassCastException”违例。

为什么呢?我们也来分析一下,不错,我们虽然从CLASSPATH外使用我们自己的classloader动态载入了这个Class,但将它的实例造型的时候是JVM会使用system classloader来再次载入这个Class,并尝试将使用我们的自己的classloader载入的Class的一个实例造型为system classloader载入的这个Class(另外的一个)。大家发现什么问题了吗?也就是我们尝试将从一个classloader载入的Class的一个实例造型为另外一个classloader载入的Class,虽然这两个Class的名字一样,甚至是从同一个class文件中载入。但不幸的是JVM 却认为这个两个Class是不同的,即JVM认为不同的classloader载入的相同的名字的Class(即使是从同一个class文件中载入的)是不同的!这样做的原因我想大概也是主要出于安全性考虑,这样就保证所有的核心Java类都是system classloader载入的,我们无法用自己的classloader载入的相同名字的Class的实例来替换它们的实例。

看到这里,聪明的读者一定想到了该如何动态载入我们的Class,实例化,造型并调用了吧!

那就是利用面向对象的基本特性之一的多形性。我们把我们动态载入的Class的实例造型成它的一个system classloader所能识别的父类就行了!这是为什么呢?我们还是要再来分析一次。当我们用我们自己的classloader来动态载入这我们只要把这个Class的时候,发现它有一个父类Class,在载入它之前JVM先会载入这个父类Class,这个父类Class是system classloader所能识别的,根据委托机制,它将由system classloader载入,然后我们的classloader再载入这个Class,创建一个实例,造型为这个父类Class,注意了,造型成这个父类 Class的时候(也就是上溯)是面向对象的java语言所允许的并且JVM也支持的,JVM就使用system classloader再次载入这个父类Class,然后将此实例造型为这个父类Class。大家可以从这个过程发现这个父类Class都是由 system classloader载入的,也就是同一个class loader载入的同一个Class,所以造型的时候不会出现任何异常。而根据多形性,调用这个父类的方法时,真正执行的是这个Class(非父类 Class)的覆盖了父类方法的方法。这些方法中也可以引用system classloader不能识别的Class,因为根据全盘负责原则,只要载入这个Class的classloader即我们自己定义的 classloader能够定位和载入这些Class就行了。

这样我们就可以事先定义好一组接口或者基类并放入CLASSPATH中,然后在执行的时候动态的载入实现或者继承了这些接口或基类的子类。还不明白吗?让我们来想一想Servlet吧,web application server能够载入任何继承了Servlet的Class并正确的执行它们,不管它实际的Class是什么,就是都把它们实例化成为一个Servlet Class,然后执行Servlet的init,doPost,doGet和destroy等方法的,而不管这个Servlet是从web- inf/lib和web-inf/classes下由system classloader的子classloader(即定制的classloader)动态载入。说了这么多希望大家都明白了。在applet,ejb等容器中,都是采用了这种机制.

对于以上各种情况,希望大家实际编写一些example来实验一下。

最后我再说点别的, classloader虽然称为类加载器,但并不意味着只能用来加载Class,我们还可以利用它也获得图片,音频文件等资源的URL,当然,这些资源必须在CLASSPATH中的jar类库中或目录下。我们来看API的doc中关于ClassLoader的两个寻找资源和Class的方法描述吧:
        public URL getResource(String name)
        用指定的名字来查找资源,一个资源是一些能够被class代码访问的在某种程度上依赖于代码位置的数据(图片,音频,文本等等)。
               一个资源的名字是以'/'号分隔确定资源的路径名的。
               这个方法将先请求parent classloader搜索资源,如果没有parent,则会在内置在虚拟机中的classloader(即bootstrap classloader)的路径中搜索。如果失败,这个方法将调用findResource(String)来寻找资源。
        public static URL getSystemResource(String name)
               从用来载入类的搜索路径中查找一个指定名字的资源。这个方法使用system class loader来定位资源。即相当于ClassLoader.getSystemClassLoader().getResource(name)。

例如:
   System.out.println(ClassLoader.getSystemResource("java/lang/String.class"));
的结果为:
   jar:文件:/C:/j2sdk1.4.1_01/jre/lib/rt.jar!/java/lang/String.class
表明String.class文件在rt.jar的java/lang目录中。
因此我们可以将图片等资源随同Class一同打包到jar类库中(当然,也可单独打包这些资源)并添加它们到class loader的搜索路径中,我们就可以无需关心这些资源的具体位置,让class loader来帮我们寻找了!
posted @ 2006-08-20 17:28 Long Long Ago 阅读(12560) | 评论 (5)编辑 收藏

最近做的gef编辑器在删除时遇到了一些问题,就是不能通过delete键删除,到处搜集资料,解决了,
首先需要在相应rcp工程中的ActionBarAdviser类中注册相应的Action,比如对应于deleteAction,我在方法org.eclipse.ui.application.ActionBarAdvisor#makeAction(IWorkbenchWindow)中注册deleteAction,如下:
protected void makeAction(final IWorkbenchWindow window){
    IAction delAction 
= ActionFactory.DELETE.create(window);
    register(delAction);
}


只是这么设置还是不能删除相应的图形元素,需要在相应的编辑器中重载init方法,添加如下的代码
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
    
// TODO Auto-generated method stub
    super.init(site, input);
    ActionRegistry registry 
= getActionRegistry();
    IActionBars bar 
= site.getActionBars();
    String id 
= ActionFactory.DELETE.getId();
    bar.setGlobalActionHandler(id,registry.getAction(id));
    bar.updateActionBars();
}

在这里仔细研究会发现,在第一段代码中实际上时创建了一个action,这是一个RetargetAction,而在super.init()方法会调用一个createAction方法,这里创建的是gef默认的redoAction undoAction selectionAction deleteAction saveAction.需要注意的是RetargetAction是一种可以跟踪当前活动的部分,由于retargetAction含有一个id,如果这个活动部分提供的handler的id和retargetAction的id相同,那么相应的对retargetAction的调用就转嫁成对当前这个活动部分的handle的调用,(当然如果根本就没有handle,那么这个action会disable).所以,我们可以看出来,这个retargetAction会在gef编辑器激活后调用gef的deleteAction.
posted @ 2006-08-19 17:35 Long Long Ago 阅读(1171) | 评论 (0)编辑 收藏

http://www.approc.com/

[Java|Eclipse|GEF]Figure是如何定位和设置大小?

有两种会创建图元:
1、打开图形文件时,先会创建所有的model,这样图元的定位和设置大小是依据图元model的size和location属性,通过图元EditPart.refreshVisuals()来设置的
2、通过拖放托盘里面的图标来建立的图元,会先设置model的size,然后通过model对应的figure.setPreferredSize()来设置了,而location则是通过鼠标的位置来确定的
posted @ 2006-08-14 11:18 Long Long Ago 阅读(660) | 评论 (0)编辑 收藏

http://www.approc.com/
现在在3.1里面访问资源文件变得比较简单了,用户可以通过继承osgi.util.NLS,典型例子:
public class MsgBoundle extends NLS{
      
private final static String BOUNDLE_NAME 
                       
= "resource.msg";
      
public static String mo_1;
      
public static String mo_2;

      
static{
         NLS.initializeMessage(BOUNDLE_NAME,
                       MsgBoundle.
class);
      }

}


好啦,现在就可以建立资源文件了,文件应该建在哪里呢?跟踪代码发现,这个由BOUNDLE_NAME决定,在MessageResourceBoundle.buildVariants()中,会将BOUNDLE_NAME中的“."替换成"/",然后再根据地区设定组合几种不同的资源文件名称,比如我的就是:

1、resource/msg_zh_CN.properties
2、resource/msg_zh.properties
3、resource/msg.properties
注意:这三个文件是有顺序的哦

然后通过EclipseClassLoader.getResource()来查找这些文件,并将文件中的值赋予给MsgBoundle对应的成员变量。
posted @ 2006-08-14 11:16 Long Long Ago 阅读(821) | 评论 (0)编辑 收藏

在linux(ubuntu)下引入了在windows工作时建立的一个pluginProject,但是发现,在plugin.xml中require包都没有了,在3.0以后这些require都是放在MANIFEST.MF文件中的,这两个文件是同步的,即在编辑plugin.xml文件视图时会看到MANIFEST.MF文件中的require,不过在从windows引入后就看不到了,不过这是发现MANIFEST.MF和他所在的目录META-INF都变成了小写,改成大写后,再update classpath就可以了。
posted @ 2006-07-19 10:26 Long Long Ago 阅读(328) | 评论 (0)编辑 收藏

http://dev.eclipse.org/mhonarc/lists/gef-dev/msg00183.html
Ask:
How to add a double click event or Request on a figure?when double click on a figure, a dialog pop up and do some actions.for example when double click on
a UML class figure, a dialog pop up, user can Add a method to the class.
Answer:
This is already done in GEF. Your editpart will receive a performRequest(req) with the type RequestConstants.REQ_OPEN.
See SelectEditPartTRacker#performOpen()

-randy
就是在figure对应的editpart中重载方法performRequest(Request req),并判断req的type是否为RequestConstants.REQ_OPEN,如果是,则处理这个双击事件.
这个方法是个回调方法,在SelectEditPartTRacker#performOpen() 中调用的.
posted @ 2006-06-08 22:21 Long Long Ago 阅读(1366) | 评论 (0)编辑 收藏

在GEF中一个容器图形,其中可以盛放其他图形,但我在实现是总是在坐标计算上出问题,就是其子图形的坐标并不是相对于此容器的坐标,而是画布的坐标,后来参考了Logical的例子,发现它在org.eclipse.gef.examples.logicdesigner.figures.CircuitFigure类中重载了useLocalCoordinates方法,返回了true(默认是false),在我的容器图形(继承于org.eclipse.draw2d.Figure)也重载这个函数,子图形的坐标问题就解决了.
posted @ 2006-05-15 14:04 Long Long Ago 阅读(702) | 评论 (0)编辑 收藏

获得插件的绝对路径:
    /**
     * 得到插件的绝对路径
     * 
     * 
@return 路径
     
*/

    
public static String getPath() {
        String path;
        
try {
            path 
= FileLocator.toFileURL(
                    Platform.getBundle(pluginId).getEntry(
"")).getPath();
            path 
= path.substring(path.indexOf("/"+ 1, path.length());
        }
 catch (IOException e) {
            path 
= "";
            e.printStackTrace();
        }

        
return path;
    }
其中FileLocator#toFileURL是在3.2中添加的,代替了原来的Platform#asLocalURL,并且对于FileLocator#toFileURL,if the plugin was a jar, it will extract the file into a temporary location
posted @ 2006-05-01 22:45 Long Long Ago 阅读(748) | 评论 (0)编辑 收藏

     摘要: http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=125&threadID=27195 Design Pattern Practice 1.序 本文从一个简单的多列排序的例子入手,由浅入深地讲解Design Pattern(设计模式)的目的、分析和实践。 文中的例子用到Compositor Pattern和Decora...  阅读全文
posted @ 2006-04-24 09:23 Long Long Ago 阅读(414) | 评论 (0)编辑 收藏

上文提到的GEF工作过程中还有一些不太详细的地方,这里做些补充。
再GEF中UI的操作交互都是通过构建Request,并调用EditPart中的API来完成相应的操作,这些API可以分为四类:
1。  EditPart getTargetEditPart(Request)
   boolean understandsRequest(Request)
再我们进行交互操作时,首先要确定激活的是那个EditPart,经常是一个当前被选中的viewer和通过单击选择的EditPart的复合体。这个被选择的部件通过询问每个被选择的EditPart是否处理这个request(通过understandsRequest方法)。那个在鼠标单击确定的EditPart是通过viewer的帮助和getTargetEditPart方法来找到的。并不是所有的交互都有target。
2。
void showSourceFeedback(Request)
void eraseSourceFeedback(Request)
void showTargetFeedback(Request)
void eraseTargetFeedback(Request)

在我们的交互操作特别是托拽图形时,就需要EditPart来回显(Feedback),托拽源被认为是起作用的那个editpart,目的部件被认为是鼠标的目的地。showXXXFeedback可能是在鼠标移到目的地,但未作用于目的part时调用,eraseXXXFeedback则可能是在鼠标作用于目的part后调用的。
3。Command getCommand(Request)
这个方法用于获得可执行的命令列表,命令可以对模型进行操作,Editpart通过request询问Editpolicy来获得相应的command列表。同时command也可以判断这个交互操作是否时可能的。如果没有command或者command不能执行,则ui会指出这个操作是不能执行的(鼠标变成禁止操作的样子)。如果一个editpart返回null作为command则它不会阻止这个交互操作,除非没有一个command被提供。为了指示某个操作是不能执行的则EditPart需要返回一个不能执行的command。
4。void performRequest(Request)
还有一种API,它让EditPart去do something,可能是一些并不会立即就改变model的改变,比如打开对话框或者激活直接编辑模式。

        EditPart并不直接进行编辑,它将操作委托给EditPolicy,每个EditPolicy专著于一种编辑任务或者一组相关的操作。当上面的API被调用后(除了performRequest方法),EditPart便委托给它的EditPolicy来处理这个request。对于不同的方法,EditPart可能是在第一个能处理request的policy处就停止调用了或者每个policy都有机会执行这个request。GEF提供了几个默认的policy,不过还是需要实现其中的一些特定方法。

         这里再废话一点,在注册相应的EditPolicy时,需要指定Role,这是一个字符串,用来标示这个EditPolicy,相同的Role对应的EditPolicy会被替换,例如子类可以通过Role这个Key来覆盖其父类所安装的EditPolicy。EditPolicy(Role)分成两种类型,一种是图形的,一种是非图形的,上文中有提到,这里详细的描述一下:
Non-Graphical Roles:
1) COMPONENT_ROLE: 一个Component存在于一个parent中,并且可以从parent中删除。更为一般的,它可以使任何只涉及到这个EditPart,而与View无关的东西。(More generally, it is anything that involves only this EditPart.)
2) CONNECTION_ROLE 这是ConnectionEditParts应该有的一个基本角色。Connections同Components有一点不同,删除Connections时通常还需要其从其source和target节点中删除,而不是从其parent中删除。
3) CONTAINER_ROLE 大部分拥有children的EditParts都应该具有这个角色。一个Container会涉及到adds/orphans以及creates/deletes等操作。
4) NODE_ROLE 如果一个EditParts用户Connection,则其应该具有这个角色,它可以用来创建,删除,重新连接一个Connection。
Graphical Roles:
1) PRIMARY_DRAG_ROLE: 用来允许用户拖动这个EditPart。用户可以通过点击这个EditPart然后拖动,或者点击这个EditPart所创建的一个Handle来进行拖动。
2) LAYOUT_ROLE: Layout角色用来放在一个Container的EditPart上,这个EditPart拥有一个graphical layout。如果这个layout有constraints,则它需要通过计算来得到这个constraints。
3) GRAPHICAL_NODE_ROLE: A node supports connections to terminals. When creating and manipulating connections, EditPolicies with this role might analyze a Request's data to perform "hit testing" on the graphical view and determine the semantics of the connection. 用于支持连接到重点的node,当创建、操作连接时,包含这个Role的Editpolicy会分析request的数据,来在图形view上做“hit testing”,并且判断这个connection的语义。
4) CONNECTION_ENDPOINTS_ROLE: 这个Role允许用户拖动一个ConnectionEditPart的端点。
5) CONNECTION_BENDPOINTS_ROLE: 这个Role允许用户能够在一个Connection中间拖动和创建bendpoints。
6) SELECTION_FEEDBACK_ROLE: 这个角色只是用来显示feedback。当鼠标进入或者在一个EditPart上暂停时,Selection Tool会发送两个类型的request给EditPart。安装了这个角色的EditPart能够在此时接受这些请求来改变view的样子,或者弹出tip,label等。
7) TREE_CONTAINER_ROLE: SWT Tree的Layout Role。 本地SWT Tree的Layout Role的等价物。这个EditPolicy应该在树上显示反馈并计算索引,就像在file explorer里拖拽一样。
over。

posted @ 2006-04-09 14:38 Long Long Ago 阅读(742) | 评论 (0)编辑 收藏

        GEF中的起到关键作用的是EditPart,而EditPart中起到关键作用的是EditPolicy,现在来描述一下GEF是如何创建一个图形并显示显示出来的。
        首先通过单击调色板,会调用一个实现org.eclipse.gef.requests.CreationFactory接口的工厂类,这个是我们在注册相应的ToolEntry时声明的,通过传入的参数这个工厂类就知道应该创建一个什么类型的model元素。(注意,这时候因为我们只是纯创建了一个模型元素,对于它的父元素,位置信息等等都还不清楚,所以一般我们在设置模型元素的构造函数时需要有个无参数的构造方法(或者有默认参数的构造函数)。)
        当单击画布上的某个位置后,相应的信息比如 模型的父元素,位置信息等等都已经可以确定,将这些信息以及新创建的模型元素本身包装成request传给相应的EditPart(就是鼠标单击处所在的图形元素对应的EditPart)。
        这个EditPart会将这个request传给相应的EditPolicy(至于怎么传递,还不清楚,估计是这样的,EditPart会传给注册在其上的所有EditPolicy,而每个EditPolicy都有个getCommand方法,通过request的type交给相应的getXXXCommand方法。查看了一下源代码,发现在AbastractEditPart中有个getCommand方法正是将每个EditPolicy中的getCommand方法都调用了一遍)。
       然后EditPolicy处理相应的request,具体的就是调用getXXXCommad方法得到处理相应request的Command,Command是可以改变模型。上面提到的这种EditPolicy属于NonGraphical类型,GEF中还包括了一种称为Graphical类型的EditPolicy,这种EditPolicy不对模型进行处理,只是用来修改图形显示(似乎这种说法不正确,它可以更改视图元素的位置,在业务模型和视图模型统一的情况下,这种图形EditPolicy也会更改模型);而NonGraphical的EditPolicy则是不清楚图形元素的,但它可以对模型进行更改,因此这种EditPolicy在不同view之间是可以通用的。
       当模型发生改变后,注册再其上的EditPart将会得到相应的通知,此时,通过通知中的property信息,EditPart会refreshVisual或者refreshChildren,从而使视图发生改变。
posted @ 2006-04-09 12:33 Long Long Ago 阅读(612) | 评论 (0)编辑 收藏

最近在用javasvn做一个svn的管理程序,用rcp实现,在实现过程中发现,如果只是单纯的用java程序实现目录列表的时候,一切正常,但使用rcp后始终出错,认证错误,观察javasvn源代码后发现,在定义认证manager的时候javasvn会检测是否在使用eclipse,如果是,则定义一个eclipseAuth××manger,后来修改了相应的代码,不调用那个检测是否为eclipse的方法后就ok了,估计是在eclipse中使用javasvn会有特殊性,故加了此段代码,不过简单的rcp是不需要的。

posted @ 2006-03-23 11:22 Long Long Ago 阅读(758) | 评论 (1)编辑 收藏

Draw2D学习
Draw2D is a lightweight system.轻量级系统,指这个绘图系统全部建立在一个重量级的控件上。对于Draw2D他是建立在SWT的Canvas上的。
Draw2D的几个部分
1.figures
主要功能:
   1。给一个figure注册和去注册监听器。这个figure会通知在这个figure中的鼠标时间给监听器。
   2。结构化的事件。比如figure层次的结构化变化,figure的移动和大小调整。
   3。当光标从figure上移过时,光标的改变和显示。
   4。在figure的层次中操作figure的位置,包括添加移除访问子节点,或者访问他们的父节点。
   5。存取:figure的layout manager;figure的size和location;还有tooltips。
   6。设置获得焦点。
   7。设置figure的透明度和可视性。
   8。进行坐标变换,figure的交叠和碰撞检测。
   9。绘制。
   10。确认。
figure有很多subclass,提供了很多附加的功能。比如
  1。shape,它包含了非矩形的figure,可以知道如何填充,并提供了对边界的宽和类型的配置,并提供了异或的绘制方法。比如有椭圆,几何线,多边形,矩形,圆角矩形和三角形。
  2。widget,draw2d包含的figure允许你创建轻量级(lightweight)的部件(widget),从而在你的draw2d应用程序中需要输入控制时提供支持。这包含大量的按钮,选择框和文本图形,标签。
  3。layer and pane,这些使用来作为子类的容器,他们提供了缩放,滚动和讲figure放置在不同layer上的能力。
图形上下文(the graphics context)
  当一个figuer需要被绘制的时候轻量系统会调用fiuger的一个paint方法。每一个figure都会得到一个graphical context,他是Graphics的一个实例。作为参数传给paint方法。这个绘图上下文支持图形操作包括绘制,填充图形绘制它的文字。它也提供了图形的状态,这些可以影响图形操作。这些状态包括当前字体,背景前景颜色等等。
2.LightweightSystem
LightweightSystem时draw2d的核心。它提供了SWT Canvas控件和在其上建立的draw2d系统之间的映射。包含三个方面:
  1。the root figure.这个是LightweightSystem$RootFigure类的一个实例。这个是用户的root figure的父类。它继承了一些基于SWT Canvas的图形环境,比如字体,前景背景颜色。
  2。the event dispatcher:SWTEventDispatcher类将SWT事件传给Draw2D的root figure中相应的部件。
  3。the update manager.它负责绘制并更新Draw2dfigure。当一个绘制请求从下层的SWTcanvas传来时,LightWeightSystem将会调用updatemanager中的performUpdate方法。update manager将会维护一个非法的或者需要重画的figure的worklist。upate manager会设法尽量连续的它的work list,这样可以尽量的提高效率。默认的update manager:DaferredUpdateManager允许通过再Display上的用户线程来使工作队列异步的更新。
对于一个figure的生存周期中,绘制(painting)和确认(validating)是主要的处理过程。draw2d会要求一个figure,调用绘制方法来递归的绘制自己。paint()方法会调用多个绘制方法:
  1。paintFigure():figure递归的绘制自己
  2。paintclientarea(): figure递归的绘制子图
  3。paintborder():figure绘制边界。
 当一个figure的size或者location需要重新计算时,将会调用确认。
  1。validate():要求figure的layout manager去重新布局它的子图
  2。revalidate():调用invalidate;添加一个图形和它的祖先去更新update manager的invalid list。
posted @ 2006-02-23 22:40 Long Long Ago 阅读(1049) | 评论 (0)编辑 收藏


现在来讨论一下emf所生成的几个plugin。一般通过ecore模型可以生成三个插件分别是emf模型,emf.edit和emf.editor。
让我们现在看模型插件
    对于所有的EPackage,都会生成两个或三个java package,本别是base package(*),implemention package(*.impl),tool package(*.util)。其中,第三个包是可选的,这取决于生成属性的设置,默认是生成。包名(*)也是在生成属性中设置的。
    对于所有EClass,在base package中生成相应的interface,而其java实现则在impl包中,如果一个EClass继承于另一个EClass,那么生成的interface和implemention都继承于相应的超类的interface和implemention。如果这个类有多个超类,那么在eSuperTypes中的第一个class将作为主超类(primary supertype)。对于这个子类的实现它将继承主超类的实现,并且实现其他超类接口中的方法。
    对于Feature,getter和setter方法在类和接口中被定义。如果一个一个feature(成员变量)不是volatile,那么它的值会被存储在一个量值(field)中。如果一个feature是只读的,那么只生成它对应的getter方法。对于多值属性一般使用EList表示,而单值就用那个属性的类型表示。EList的类型取决于模型的约束,例如,一个non-containment reference将会使用EObjectWithInverseResolvingEList,对于一个containment reference将会使用EObjectContainmentWithInverseEList表示。
    对于Operation,在包含类(containing class)的接口中生成一个公共方法标签,在对应的实现中生成实现骨架。
    对于DataType,其中EEnum产生于一个继承了org.eclipse.common.util.AbstractEnumerator的实现。对于其他的EDataType,是没有接口和实现生成的,它们的实例化类就是直接使用了EAttribute的类型。
再来看看edit插件:
    对于edit插件provider package中的所有类都有相应的ItemProviders类生成。更进一步,对于整个插件有一个EMFPluginClass生成。ItemProvider类继承于org.eclipse.emf.edit.provider.ItemProviderAdaptor,用于适配模型中相应的EObject(所有emf类的基类)。当模型对象由于fireNotifyChanged()改变,ItemProvider会传送相应的通知,并过滤其他的。当你生成插件时,你可以控制哪些通知被过滤。
    ItemProvider也管理属性描述(property descriptors)对于所有的featur of the class,通过getImage和getText方法来管理类的icon和descrition。
    对于所有的ItemProvider都有一个ItemProviderAdaptorFactory。
最后时editor插件:
    对于所有的模型都会再presentation package中生成三个类。
     一个多页编辑器,它给模型创建几个不同的jface viewer,包含一个TreeViewer,使用edit插件中的ItemProvider作为这个treeviewer的content和label的provider。这个editor还创建outline和property来显示在viewer中选中对象的属性。
     一个ActionBarContributor,它被用于对编辑器视图中选中的item所创建的context menu添加选项。
     最后是一个向导,允许你创建一个包含模型对象的一个实例的资源(resource)。

   

 

posted @ 2006-02-19 20:46 Long Long Ago 阅读(673) | 评论 (0)编辑 收藏

EMF中的三个部分:元数据meta-data,代码生成code generation,默认序列化default serialization.
EMF is part of the Model Driver Architecture(MDA).MDA是将整个应用软件生存周期的开发管理都集中于模型上。这种模型是用元模型(meta-model)定义的。然后,使用映射,模型被用于生成software artefacts, 这就实现了整个系统。
EMF所能做的:emf能够用于描述和构建模型。所实现的模型可以被用于任何java应用程序的开发。
对于ecore模型我们有几点需要注意:
1。在ecore文件的xmi描述中没有各个元素之间的联系,我们使用EReference来表示各个元素之间的联系。
   对于两个方向上的联系,通过两个成对的EReference来表示,每个联系的类,都有一个eOpposite来保存它自己在所联系类中的名字(引用)。
   对于单方向上的联系,使用一个单独的EReference,并且这个reference没有eOpposite(因为对方并没有对自己的引用)。
   连接的多重性通过upperBound和lowerBound来表示。多个用-1。
2。使用EPackage可以像java中的package一样用。
3。两种模型,一种是业务模型,一种是视图模型。一般将两种模型放在不同的package中。两种方法将两种模型联系起来:
   一是构建一个新package,构建一个多重继承的新package来将两个package联系起来。
   二是将两种模型分开存储,添加从视图模型到业务模型的引用。这种方法两个模型是松耦合的。这种单方向的引用也不会破坏(污染)业务模型。注意,由于这种引用是两个package之间的,所以在移用的eType类型中需要指明包名。  

除此之外,ecore还提供了一种递归的定义方法。就是使用eSubPackage来分别包含业务模型和视图模型,并且在这两个subPackage间建立reference(对于package来说是自己引用自己)。
4。我们可以有多种方法来生成ecore模型。通过注释的java接口(annotated java interface),通过rantional rose创建模型,通过XML Schema等等。

posted @ 2006-02-19 15:32 Long Long Ago 阅读(621) | 评论 (0)编辑 收藏

上文已经说过了,GMF中,那些generator,definition,mapping model都是最后要合成为diagram runtime(or notation)model。
以下是简单的步骤:
简单的使用方法,首先应该定义ecore模型文件有很多种方法,比如用EclipseUML插件,或者使用gmf的example种的Ecore Diagram,这些图形化的类图编辑工具都可以方便的生成我们所需要的Ecore文件。这里我们使用的是EclipseUML的免费版本。是针对eclipse3.1的,不过好像3.2m1m2都可以用。
生成ecore文件后 File/new/other/Eclipse Modeling Framwork/EMF Model 生成相应的genmodel文件,通过它来生成模型和.edit插件,这些都是GMF所需要的。然后再File/New/other/Example EMF Model Creation Wizards/GMFGraph Model 注意名字应该都是一样的(扩展名不同)最后的Model Object选择Canvas。在生成的gmfgraph文件中给canvas命名,canvas就相当于我们的画布。在canvas下创建一个figure gallery(图库),在这里我们保存所需要的图形。
然后创建gmftool文件File/New/other/Example EMF Model Creation Wizards/GMFTool Model,可以使用File/New/other/GMF/GMFTool Simple Model来简单的构造。
再创建gmfmap文件,建立模型和图形之间的映射关系。File/New/other/Example EMF Model Creation Wizards/GMFMap Model,名字应该和前面的一样,最后的Model Object选择Mapping。在生成的gmfmap文件中右键选择Load Resource,选择ecore,gmfgraph,gmftool文件。并且生成相应的映射信息,比如node mapping和canvas mapping。
最后生成emf模型代码和edit代码,使用genmodel文件。
使用gmfmap文件右键生成gmfgen文件,注意我们要有genmode文件才能生成gmfgen文件。
最后用gmfgen生成editor插件。这时候共有三个插件 模型 edit和editor。
使用时File/New/other/Example/中有个相应的diagram,一般再最上面,给一个名字,无扩展名,就打开相应的编辑器了。
posted @ 2006-02-15 10:37 Long Long Ago 阅读(939) | 评论 (1)编辑 收藏

GMF有可能成为一个标准的Eclipse图形模型编辑器,它分为两个部分:运行时(the runtime)和工具(tooling),工具是由一些创建和编辑符号语义模型的编辑器组成的,工具还包含了图形编辑器,并且有图形编辑器的生成器。

使用GMF开发一个类图形模型编辑器的步骤如下:

1。创建域模型,这个模型描述了通过编辑器编辑的非图形信息。
2。创建图定义模型,这个模型描述了显示在图形编辑器中的图形元素。
3。创建图映射模型,这个模型定义了域模型元素和图形元素之间的对应关系。
4。生成图形编辑器。
5。通过改进生成的代码来提高图形编辑器。
介绍一点GMF的图形编辑器的特性:
1。折叠
2。label的直接编辑
3。提示
4。弹出bar
5。连接柄
6。通用绘图工具
7。通用菜单命令
8。通用工具条
9。自动缩放和布局
10。通用属性编辑
11。打印和打印预览
12。SVG和剪贴板支持

http://www.eclipse.org/articles/Article-Introducing-GMF/article.html

posted @ 2006-02-14 11:38 Long Long Ago 阅读(1155) | 评论 (0)编辑 收藏

uo       最近在学习GEF,本例子主要是修改了八进制的例子。在这里做一下总结。先来介绍一下gef,网上关于GEF的介绍越来越多,从另一方面也说明这个技术确实在热起来,不过GEF为了实现可扩放性和可重用性,做的很是复杂(闲话一下,最近看了一本书《Better.Faster.Lighter.Java》,没看多少,Justin Gehtland, Bruce A. Tate写的,一开始就说了,为了满足越来越多的需要,各种framework,library,language都是越来越复杂了,学习曲线也变得陡峭了,这个似乎是无法避免的,java语言也是,不信去看看jdk,1.1时才3.7MB,1.4时已经38MB了。我也一直坚信,Simply the best,呵呵)。
       GEF的一些资源:
          1。www.eclipse.org 这个是源头啊,faq也可以看看 
          2。ibm的dW 除了相关文章,这里还有本关于gef和emf的红宝书,可以免费下载
          3。eclipseworld.org 这个是一个关于eclipse中文社区
          4。八进制的blog http://bjzhanghao.cnblogs.com/ 。
          5。这里还有些中文教程 http://www.benisoft.com/cn/index.htm
          6。还有一些blog http://www.blogjava.net/hopeshared/  http://www.blogjava.net/eclipshine/ http://www.blogjava.net/reloadcn/ http://www.blogjava.net/USTCEric
      好了,现在来介绍一下GEF的实现机制,主要是我的一些理解可能还有一不对的地方,欢迎大家帮我改正。
      GEF是具有标准MVC(Model-View-Control)结构的图形编辑框架,其中Model由我们自己根据业务来设计,它要能够提供某种模型改变通知的机制,用来把Model的变化告诉Control层;Control层由一些EditPart实现,EditPart是整个GEF的核心部件;而 View层,大多数情况下,使用Draw2D实现了,其作用是把Model以图形化的方式表现给使用者,当然你也可以使用其他图形包,不过gef与 Draw2D的结合还是非常紧密的,一般为了使用方便,View都是使用Draw2D实现的。
        关于Draw2D,八进制有一篇相应的文章,还有eclipse上的一篇文章,可以参考,这里有几点需要强调一下:
         1。Draw2D提供了很多缺省图形,最常见的有三类:1、形状(Shape),如矩形、三角形、椭圆形等等;2、控件(Widget),如标签、按钮、滚动条等等;3、层(Layer),它们用来为放置于其中的图形提供缩放、滚动等功能。
        2。每个图形都可以拥有一个边框(Border),Draw2D所提供的边框类型有GroupBoxBorder、TitleBarBorder、ImageBorder、 ButtonBorder,以及可以组合两种边框的CompoundBorder等等,在Draw2D里还专门有一个Insets类用来表示边框在图形中所占的位置,它包含上下左右四个整型数值。
        3。一个图形可以包含很多个子图形,这些被包含的图形在显示的时候必须以某种方式被排列起来,负责这个任务的就是父图形的LayoutManager,相应的约束(Constraint)对象就在这里,比如在子图形刷新(refreshVisuals)时可以通过将其布局约束传递给其父元素来定位。
        4。利用Draw2D中的Router、Anchor和Locator,可以实现多种连接样式。Router负责连接线的外观和操作方式;Anchor控制连接线端点在图形上的位置,即"锚点"的位置;Locator的作用是定位图形。
        前面说过了GEF是通过MVC架构实现的,下面分别介绍这三个方面:
        一、模型(Model):这个是相对比较容易理解,模型保存着业务逻辑,他是GEF这中唯一可以持久化的,并且需要提供相应的监听机制,来通知 EditPart去更新View,监听机制又可以分为分布式和集中式两种,我们可以针对域监听器进行转有通知,也可以全局广播;在这里我们认为 Command(EditPart用来更新模型的对象)也是模型的一个部分,因为它了解模型。此外,在有时候可能需要多种模型。我们对于业务模型,提供一个“视图”模型,这样便于实现和满足相应的要求,不管内部模型是怎么样的,都要实现通知机制。
      二、视图。GEF提供了在EclipseWorkbench中使用Viewer,称为EditPartViewer。它的作用和jface中的 Viewer很相,不过这时候它的ContentProvider和LabelProvider是EditPart,通过setContents设置(注意在八进制代码中setContents参数为模型对象,通过分析发现,在AbstractEditPartViewer中此方法被重载,可以除了接受 org.eclipse.gef.editpart对象外可以接受Object对象,不过此时回调用EditPartFactory,通过给定的模型对象来创建相应的EditPart,个人认为这样做并不与“View与Model无直接联系”的要求相矛盾)。这个接口继承了 org.eclipse.jface.viewers.ISelectionProvider接口(这个也和jface中的viewer一样),因此使 viewer提供了监听器,这样可以将对view的操作传递到editpart。EditPartViewer会维护各个EditPart的选中状态,如果没有被选中的EditPart,则缺省选中的是作为contents的EditPart。
         EditPartViewer可以适配到任何一个SWT控件,因为它要求有createControl方法,接受一个父SWT控件作为适配对象,从而将 GEF生成的Figure对象显示在这个SWT控件上,因此GEF可以使用任何图形包,不过GEF提供了对Draw2D的默认实现,比如 AbstractGraphicalEditPart类中的回调方法createFigure就要求返回一个IFigure对象。将一个GEF图形显示出来可以使用org.eclipse.ui.part.EditorPart也可以是org.eclipse.ui.part.ViewPart,如果使用 EditorPart,则需要实现继承自WorkbenchPart的createPartControl,如果是ViewPart则需要自己实现与 GEF的交互,不过Eclipse Workbench对viewpart和editorpart的支持是一样的,都是在plugin文件扩展相应的扩展点。一般来说,使用GEF对 Editorpart的模式实现是非常方便的。
      GEF目前提供两种视图分别是GraphicalViewer和TreeViewer,都间接继承了EditPartViewer接口,前者利用 Draw2D图形(IFigure)作为表现方式,多用于编辑区域,后者则多用于实现大纲展示,并且他们有两个具体的实现,分别是 ScrollingGraphicalViewer和TreeViewer。其中ScrollingGraphicalViewer,通过分析GEF源代码可知,是GraphicalEditor中Viewer的默认实现,我们可以通过GraphicalEditor类提供的 setGraphicalViewer(GraphicalViewer)方法重新设置其他viewer。我们在使用的时候通过 configureGraphicalViewer和initializeGraphicalViewer方法来配置GraphicalViewer。所不同的是,在configureGraphicalViewer之后initializeGraphicalViewer之前插入了一个 hookGraphicalViewer方法,其作用是同步选择和把 EditPartViewer作为SelectionProvider注册到所在的site,使其成为SelectionProvider。
       此外,GEF中还提供一种成为调色板(PaletteViewer)的视图,它也是间接实现了EditPartViewer接口。因此,我也像配置GraphicalViewer一样来配置PaletteViewer,类似的使用 configurePaletteViewer和initializePaletteViewer方法,其间类似的插入了一个 hookPaletteViewer方法,用于设置相应EditDomain的PaletteViewer。
       最后,总结一下如何配置GraphicalView,主要是四个方面:
      1。设置一个RootEditPart:RootEditPart的是使整个GEF框架运行起来的关键之一。RootEditPart并不对应于任何的模型对象,它将从setContents()方法中接收到的模型对象进行转换,并添加到整个的EditPart体系中去。
       2。设置其EditPartFactory:负责从模型到EditPart的转换。一般来说一个模型对象对应于一个EditPart。
       3。设置EditDomain:用来接收事件并选择恰当的事件处理函数进行处理,这个主要是为了相应view所接收到的用户的操作。
       4。调用setContents()方法:为其设置要显示的内容。
       三、控制器(EditPart)。这是GEF最核心的地方了,它将model和view联系起来。程序员可以继承三种EditPart,分别是 org.eclipse.gef.editparts.AbstractGraphicalEditPart,org.eclipse.gef.editparts.AbstractConnectionEditPart(AbstractGraphicalEditPart的子类)和org.eclipse.gef.editparts.AbstractTreeEditPart。EditPart 的生命周期当setContents时,将模型对象输入,此对象为顶层对象,通过它可以遍历所有的模型对象。:EditPartViewer配备有 EditPartFactory,EditPartViewer通过EditPartFactory构造相应的Contents EditPart。然后EditPartViewer中的每个EditPart通过EditPartFactory构造相应的子EditPart。当用户添加新的模型的时候,包含这些模型对象的所对应的EditPart将构造相应的EditPart作出相应。视图的构造和EditPart的构造是同时进行的,每当EditPart构造并添加到它的父EditPart后相应的view也做同样的事情,比如GraphicalEditor中就有 GraphicalViewer成员变量,在此edipart创建时回调用createPartControl,在这个方法中将会创建相应的view,并将其加入到父view中。
       一旦用户丢弃了某些模型对象,相应的EditPart就会被丢弃。比如用户执行了删除操作,则相应的模型对象和相应的EditPart都被删除了,但用户撤销了删除操作而重新创建的EditPart与原来的EditPart是不同的,所以EditPart是不能保存长期信息的,应该交给模型去保存,并且 EditPart也是不能被Command所引用。
    在具体使用时,应该先定一个Contents EditPart,它的父元素为EditPartViewer的RootEditPart。RootEditPart是EditPartView的 root,它将EditPartView和它的contents(一般认为是顶层模型对象所对应的EditPart)联系起来,RootEditPart 为用户的EditPart提供一致性的上下文(context)。并且,当没有被选中的模型EditPart时,顶层对象的EditPart是默认被选中的,而RootEditPart是不能被选中的或者定位的,甚至不能与用户相交互,他也就没有对应的模型对象。GEF提供了几个默认的 RootEditPart的实现:FreeformGraphicalRootEditPart,RootTreeEditPart, ScalableRootEditPart,ScalableFreeformRootEditPart(此类继承于 FreeformGraphicalRootEditPart,增加了缩放Zoom支持,提供了ZoomManager;增加了一个LayerPane: ScalableFreeformLayeredPane。),(原来还有个GraphicalRootEditPart,已经deprecated,用 ScalableRootEditPart代替)。这里要注意的是:
    1)对于FreeformRootEditPart(以及它的子类 ScalableFreeformRootEditPart),它的Contents EditPart应该有一个org.eclipse.draw2d.FreeformFigure对象(比如FreeformLayer或FreeformLayeredPane)作为它的Figure。例如在我们的代码里,对应的就是在 DiagramPart中的createFigure方法,它返回了一个FreeformLayer对象。
    2) FreeformGraphicalRootEditPart的Primery Layer没有使用draw2d的LayoutManager,并且,除非contents的figure是个freeform figure,否则不能正确的调整大小。
    3)FreefromGraphicalRootEditPart使用FreeformViewport类作为它的primary figure,这个类必须和ScrollingGraphicalViewer使用。
    4)ScalableRootEditPart使用 Viewport作为primery figure,这个类必须和ScrollingGraphicalViewer使用。
    5)RootTreeEditPart是TreeViewer的RootEditPart。
    Contents EditPart的图形一般是一个空面板,该面板将包含diagram的子图。
    我们还要定义其他“视图”模型的EditPart,比如节点(node),连接(connection)。对于节点,是Graphical,可以继承 AbstractGraphicalEditPart。并且可以实现NodeEditPart接口,则可以支持源和目标的 ConnectionEditPart,使用Anchor,具体在下面有讲述。在这里,我们的代码的NodePart类中,重载了 refreshVisual方法,其中设置figure的布局约束是通过它的父元素来定位的,我不知道这是不是标准的方法,XYLayout使用的是 rectangle约束,如果长宽设为-1,表示为该图形提供理想大小。不要使用setBounds,XYLayout布局管理器可以保证对滚动条进行更新,并能将相对约束转化为绝对约束,如果长宽设置为-1,则能够将图形设置为理想的大小。此外,为了改进性能,我们可以将refreshVisual中有若干模型的分别刷新,或者设置是否需要刷新的开关。
    对于连接,可以继承AbstractConnectionEditPart,这个抽象类已经实现了 ConnectionEditPart。和Node实现一样,通过refreshVisual方法将属性从模型映射到图形。连接也可以有约束,不过与前面的约束不尽相同。在这里,连接路由器(connection router)使用约束来是连接转向(bend)。需要注意的是,一个Connection EditPart的图形必须是一个draw2d的Connection,这样就引入了连接锚(Connection Anchor)。连接必须由连接锚(ConnectionAnchor)“锚定”在两端。因此,需要在连接EditPart或者节点EditPart中实现使用什么样的锚。缺省情况下,GEF使用节点EditPart通过实现NodeEditPart接口来提供锚。这是因为,锚的选择取决与各个节点上正在使用的图形,而连接EditPart不应了解节点上的图形,并且,当用户创建连接的时候,连接EditPart并没有创建,这时候就由节点自己显示反馈。这里需要注意的是:何时使用以及何时不使用ConnectionEditPart,当用户可以选择某些东西并可与之交互时,就需要 ConnectionEditPart。它可能与某行中的某个对象相对应,并且回自己删除;如果这是需要一个绘制直线的节点或者容器,那么只需要用图形的 paint方法绘制直线,或者组合一个图形,这个图形包含Polyline图形。连接必须要拥有源和目标。如果要创建一个连接,并且在没有源和目标存在的情况下,可以继承AbstractGraphicalEditPart,并使用连接图形,比如PolylineConnection。
    正如前面说的,模型需要实现某种通知机制,各个EditPart要将自己做为监听者,注册在模型上,以便在模型发生改变的时候收到相应的通知。   当EditPart收到相应的通知时,调用相应的处理方法,来进行一次强制的刷新,对于模型对象的增减可以调用refreshChildren方法,对于简单属性的更改可以调用refreshVisuals方法,这时,像前面提到的,可以只刷新改变的部分,以提高性能。一般,我们在Activate方法中注册监听器,在Deactivate方法中注销监听器。
    以上完成的是从模型到视图的映射,你还需要实现的是编辑策略Edit Policies,因为EditPart并不直接对模型进行编辑,每一个EditPolicy都针对一个特定的编辑任务或者一组相关的工作。这样的实现便于在不同的EditPart间重用(reuse)编辑行为,并且这样的实现,可以动态的改变行为,比如改变layout或者routing时。在 createEditPolicies方法中,我们要insall我们的的Policies,这时候就需要roles,roles的作用就是作为关键字来标示不同的policies。policy中提供Command,每个应用程序都有个Command栈,Command的运行要通过Command栈,而不是直接调用execute方法。
    说到这里,要注意EditPartView是视图的,一般放在一个UI插件上比如EditorPart,viewPart,甚至swt控件。EditPart是控制器,联系视图和模型。
    在本文的最后介绍一下GEF的运行机制:EditPartView接受用户的操作,如节点的移动,增删,每个节点对应一个EditPart对象,每个 EditPart都有一组由Role区分的EditPolicies,每个EditPolicy会对应一些Command对象,Command最终会对模型进行操作。用户的操作会转化为Request分配给特定的EditPolicy,由EditPolicy创建相应的Command对模型进行操作,这些 Command会保留在EditDomain的命令堆栈中,用于实现undo,redo功能,EditDomain是专门用于维护 EditPartView,Command信息的对象,一般每个EditPart有一个EditDomain。
    以上就是对所作的GEF部分的大概描述,可能有些地方写的比较罗嗦,有的地方写的也可能不对,希望大家指出,谢谢。下面还要对RCP部分进行一些描述。
posted @ 2006-01-12 14:35 Long Long Ago 阅读(3834) | 评论 (0)编辑 收藏