Author: qingbo

  • 在Gmail顶部显示Google Reader的未读数

    我把Gmail作为Firefox的主页,打开浏览器就一直开着。我用Google Reader来读RSS.

    曾经用Google Reader Notifier这个扩展来显示未读数目,后来嫌烦,不用了,并且清理了许多无聊的RSS订阅。

    不过没有了未读提醒,有时候好多天才想起去看一眼,又觉得不爽。真是很矛盾。

    看到Gmail顶上有个Reader的链接,哎,这是个好地方,于是把未读数目显示在那里,挺好:

    当然了,这个脚本似乎就是Google Reader Notifier的缩减版,只是有趣而已……

    一般情况下8分钟检查一次。点击Reader链接打开以后,改为1分钟后检查,每检查一次,间隔时间加长一分钟,直到恢复为8分钟。

    你需要先安装Greasemonkey,然后安装该脚本

    Page on userscripts.org.

  • ActionForm中动态长度的List

    某些情况下,一个表单中提交的内容数量是不一定的。比如Facebook的编辑照片界面,可以把整个相册的照片(最多60张)放在一起进行批量编辑,或者把一次上传的若干张照片一起编辑。这种情况下,如何用ActionForm来获取这些数据呢?

    这个ActionForm(假设名为PhotoForm)应该有一个字段:

    List<Photo> photos;

    生成这个表单的时候很简单,服务器端当然清楚有多少张照片要被编辑,于是对这些照片遍历一遍,生成表单。实际上,struts form的设计考虑了链表数据的提交。以照片编辑页面为例,生成表单时,可以这样:

    <logic:iterate id=”photo” name=”photos”>
    <html:hidden name=”photo” property=”id” indexed=”true” />
    <html:text name=”photo” property=”name” indexed=”true” />
    </logic:iterate>

    indexed属性便是专为链表设计的。这样的jsp代码生成的HTML类似:

    <input type=”hidden” value=”5″ name=”photo[4].id”/>
    <input type=”text” value=”编辑前的名字” name=”photo[4].name”/>

    然而在用户将修改过的信息提交的时候,由于HTTP协议的stateless特性,服务器端已经“忘记”有多少张照片了(除非你在session中记录了这个数字,不过处于性能考虑大多数人在避免使用session,另外,假如用户在编辑信息的途中出去玩了几小时,回来继续提交的话,session已经不存在了)。

    photos这个List即使放在对应的ActionForm中也无济于事,因为用户看到表单时,这个ActionForm对象已经消失了(当然,不要在那个mapping中设置scope=”session”)。

    究其源,溯其本,很容易解决这种问题。请求提交给struts,struts首先根据mapping创建一个对应的ActionForm对象,当它看到表单中的属性名为photo[4].id,就会调用PhotoForm的getPhoto(4).setId(xxx)来填充表单。

    注意,struts不是生成一个Photo对象,把id、name填充进去,再加到photos这个List中,它不知道要生成Photo对象。它只知道利用发射机制调用getPhoto(4).setId(xxx)。

    所以我们应该在PhotoForm的getPhoto这个方法里做文章,根据参数将List扩展到适当的大小避免IndexOutOfBoundException,并且生成Photo对象保证不出现NullPointerException. 示例如下:

    public Photo getPhoto(int index) {
    	// 如果链表不存在则创建
    	if (photos == null) {
    		photos = new ArrayList<Photo>(index + 1);
    	}
    
    	// 如果链表长度不够,扩展
    	if (photos.size() < index + 1) {
    		for (int i = photos.size(); i < index + 1; i++) {
    			photos.add(null);
    		}
    	}
    
    	// 取得photo,如果为空则创建
    	Photo photo = photos.get(index);
    	if (photo == null) {
    		photo = new Photo();
    		photos.set(index, photo);
    	}
    	return photo;
    }

    这样就可以了,没有用到session. 看起来比较繁琐,其实道理很简单,明白了struts form的填充方式,就没什么困难了。

    另一个例子是客户端可以用JavaScript生成一堆这样的file input:

    <input type=”file” name=”file[0]” />
    <input type=”file” name=”file[1]” />
    <input type=”file” name=”file[2]” />
    <input type=”file” name=”file[3]” />

    struts怎么填充呢?它会首先读取文件数据,创建FormFile对象,然后调用对应的ActionForm的setFile(index, file)方法填充,index就是那个“数组下标”。所以你需要写一个setFile方法来正确处理。

  • 使用pinyin4j将汉字转化为拼音的例子

    在合肥的时候,靠骗钱维生的电脑学校很多,而且确实骗到了许多钱,于是形成了良性循环,在电视上打广告,骗更多的钱。有个广告的末尾就是一憨厚的老大爷高兴地说:只有正规,才有机会啊!

    某些情况下这句话很对。那些在网上被到处转贴的代码片段,我就不敢信赖,而且格式很乱也看不清楚。在找汉字转拼音程序的时候,我还是选择了pinyin4j,Source Forge上的项目,相对值得信赖。哦对了,这是Java的library.

    一个简单的方法,使用pinyin4j转换字符串中的汉字为拼音,示例如下:

    public static String toPinyin(String input) {
    	StringBuilder sb = new StringBuilder();
    	for (int i = 0; i < input.length(); i++) {
    		char c = input.charAt(i);
    		if (c <= 255) {
    			sb.append(c);
    		} else {
    			String pinyin = null;
    			try {
    				String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c, PINYIN_FORMAT);
    				pinyin = pinyinArray[0];
    			} catch (BadHanyuPinyinOutputFormatCombination e) {
    				logger.error(e.getMessage(), e);
    			} catch (NullPointerException e) {
    				// 如果是日文,可能抛出该异常
    			}
    			if (pinyin != null) {
    				sb.append(pinyin);
    			}
    		}
    	}
    	return sb.toString();
    }

    其中用到的常量PINYIN_FORMAT是这样初始化的(输出时去除声调标记,发音“驴”的那个韵母用v表示):

    PINYIN_FORMAT = new HanyuPinyinOutputFormat();
    PINYIN_FORMAT.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
    PINYIN_FORMAT.setVCharType(HanyuPinyinVCharType.WITH_V);

    因为汉字有多音字,所以PinyinHelper.toHanyuPinyinStringArray返回一个数组。这个方法图省事,就直接选择了第一个读音。如果可能的话,可以将遇到这种多音字的地方提出来,人工进行处理。

  • 为什么用Eclipse而不是NetBeans

    在当下这个项目开始的时候,我们尝试使用NetBeans作为开发工具。也许是由于之前一直在用Eclipse,先入为主了,我总是发现NetBeans在一些地方似乎有先天的不足。于是在一周后,项目移到了Eclipse中进行开发,到现在依然在为没有太晚做这个决定而庆幸。

    先从表面现象来比较。Eclipse的图形界面是SWT,而NetBeans作为Sun的产品,当然不好意思不用Swing了,于是给人第一印象就不好。某些系统上,dockable的窗口标题字体与标题栏高度明显不符,很不协调。输入中文时,候选窗也不能跟随光标移动——现在的Web开发一般都使用大屏幕显示器,你需要不时地看看屏幕的角落。别人可能会觉得奇怪——你有什么急事吧?要不然怎么不停地看时间呢?

    在Eclipse中我习惯打开一个资源管理器窗口,从中复制一些文件,然后切换到Eclipse窗口直接粘贴到Package Explorer合适的目录。这一习惯在NetBeans里行不通,只能在IDE内部复制、粘贴。

    然后透过现象看看本质。在NetBeans选中一个文件,按F2,你就发现,文件后缀是不能修改的!如果你想把一个.jspf改为.jsp,只能在资源管理器中做。看来NetBeans的理念就是傻瓜——比Windows还要傻瓜。

    SVN集成。NetBeans默认集成了SVN,乍一听很让人兴奋。确实,当我看到单个文件中都有图形化的svn diff的时候,兴奋地跑到隔壁去找人倾诉。不过等到commit的时候我就开始沮丧了,完全没有subclipse方便、直观(subclipse 1.4不好,到现在我仍然在用1.2)。

    最让人郁闷的是,J2EE项目中,在资源管理器里新加一个文件,在NetBeans刷新出来,部署时仍然会被忽略掉!仔细想想才明白,原来是要执行svn add才可以……合理乎?不合理乎?这svn集成,用一个词来形容就是“天衣无缝”。Web application部署都考虑到svn了,真是周到,佩服,佩服。

    NetBeans的项目配置文件太过复杂,不适合作代码管理。nbproject下一堆文件,大部分是ant build file. 如果有library引用,肯定是绝对路径。在Eclipse里,即使有定制classpath,只要手动改成相对目录就一劳永逸了,svn co到任何地方都直接打开没有任何问题。NetBeans可不一般,改成相对目录可以,只要再新加入一个jar包,已改为相对目录的还会变成原来的绝对路径。

    这不由得让人怀疑,NetBeans傻瓜吗?不,它很强大,很灵活,要不然配置文件怎么会一大坨。那这么复杂的配置文件,svn怎么管理啊?哦……svn集成很完美——web app部署都会考虑到代码管理。

    x坐标一端是傻瓜,一端是灵活;y坐标一端是天衣无缝的svn集成,一端是复杂到天衣无缝的svn集成都无能为力的配置文件。天才的NetBeans在这个二维空间里找到了那个独一无二的完美的点,就像宇宙中只有一个地球一样。我错了,我是火星人。

    Refactoring. Refactor – Encapsulate field,没有全选、取消全选的按钮。同时已有getter/setter的方法没有体现出来,但是你硬要选上的话,它又会抛出exception.

    Refactor – Rename,没有同时重命名getter/setter的选项。

    从某种程度上说,NetBeans很像MS的产品——大部分用户做傻瓜,小部分人是精英,为傻瓜们解决不应该是问题的问题。我做不了精英,却不想做傻瓜,于是还有一条路——我逃了。

    嘿嘿,有许多人推崇NetBeans。确实,NetBeans某些方面比Eclipse做得好,比如上面提到的文件内svn diff,还有对于web开发很重要的——更好的JavaScript编辑器、HTML编辑器。还有其它的我还没来得及发现就放弃了。本文也就是发发牢骚,顺带给想比较这二者的同仁们抛砖引玉。

  • Eclipse 3.4 web开发方面的改进

    说到Eclipse 3.4,大部分人只会注意eclipse核心功能的改进和新特性比如Breadcrumb。

    不久前发现Eclipse 3.4对于Web developer来说也有不少值得注意的改进,具体链接在这里

    Web开发这一块功能属于Web tools platform. 我最关心的是这一段:

    A new JavaScript IDE, called JSDT, provides the same level of support for JavaScript as the JDT provides for Java. Some of the new features include code completion, quick fix, formatting and validation. All the functions are, naturally, aware of libraries you specify for your project.

    自带的JavaScript编辑器终于可以凑合用了。

    另外,编辑XML/HTML/JSP页面时,编辑器会自动高亮匹配的标签,这一点太重要了,让我们不至于在复杂的标签中迷失。

    不过,JSP的格式化依旧很烂,另外,回车时竟然不能保持缩进了!真是有得有失。

  • 10号线开通,三里屯apple store开业

    经过不知道多少次的推迟之后,地铁10号线终于“抢”在单双号施行前开通了。我就住10号线站台不远,今天和弟弟闲着没事,就想去看看。到哪里去呢?正巧大陆第一家apple store开业,位置正好在10号线边上,这是最佳的目的地!

    大约是周末的原因,进入地铁站发现乘客很少,空调又开得很大,让人感觉异常的冷,而里面的警察、保安数目却是异常的多。

    相对1号线、2号线,10号线运行起来相当平稳。不过由于刚开始运行,难免出些小差错。上车后连续两站都是在进站前就停下,因为我坐第一节车厢,透过玻璃看见前面一辆车还停在站台上没走。第二次遇到这种情况后,列车故意延迟了一会出站。车头仪表盘:

    还有一点非常让人郁闷的是不光隧道内部,刚进地铁站,手机就一点信号都没有了。这一点比较致命,相信以后会改的,到时候应该会覆盖所有区域。

    ————-一定要有个分割线————-

    在团结湖站下车,出站后顺工体北路向西,没多远就到了Adidas的商店,从旁边进去,就看见传说中的apple store了。

    商店旁边还专门有一个做活动用的大厅,似乎比商店本身还大:

    从门口进去,店员给每个人戴一个手环,上面是排队号码:

    昨天就在网上看到有人在排队了,真是够辛苦。好在地铁10号线是下午两点以后才开通,这时排队的人相对来说已经比较少了,我们等了十几二十分钟,就进入了商店。主要是玩了玩iPod touch,看了看Macbook Air. 店里没有iPhone,不过iPod touch上面的firmwire已经是2.0版本了,主界面上有app store. 在safari中输入网址的时候,按住.com键不放,就会出来.edu, .org, .net这些选择,这个改进很不错。Air是第一次见,除了薄之外,实在是没什么吸引力。

    apple store开到中国来,还是很让人兴奋的。可是,关税啊关税……是让人从美国带回来(或者买水货)免掉高额的关税,还是在这里多花几千大洋得到优质的售后服务呢?许多用户估计都得做这个选择。也许光靠这个商店的销售是收不回高额的成本(雇员薪水、地租、明星出场费)的,不过这个商店可以让更多的人了解苹果的产品,对于扩大中国的市场,还是很有作用的。但是销售额如何也不能过早下定论,中国人平均购买力是低了点,苹果的产品是贵了点(尤其是过了中国海关),不过十几亿的人口摆在那里,富人还是很多的。

  • No LIMIT clause in HQL (Hibernate 3)

    在Hibernate 2中习惯了将limit ?, ?语句写在HQL中,使用Hibernate 3时突然发现limit语句不起作用了,hibernate生成的SQL中完全不见limit的踪影,它被忽略了!

    limit在分页中可是至关重要的工具。当然某些牛人把整个表都取出来在内存中作分页,我却没有这种魄力。

    HQL的文档中也没有提到LIMIT,可能真的是不支持了。不过我们可以用Criteria这个接口的setFirstResultsetMaxResults来实现limit。Query接口也有类似的方法。

    如果用的是Spring的HibernateTemplate,则可以调用其findByCriteria方法来实现相同的功能。

  • Eclipse 3.4/subclipse 1.4,不要太着急

    Eclipse 3.4对于Java开发者来说比较有吸引力。不过我试用了一下,使用相同的JRE的情况下,Eclipse 3.3的默认字体配置更好地显示了中英文混排的文字,而Eclipse 3.4的中文显然比英文要大上一号,不知道在哪里去更改这种行为。界面毕竟是开发者考虑的一个因素,不管比重大小,在意这一点的同志们要注意了,不要急于升级。

    差不多同时svn 1.5发布了,而subclipse 1.4就是针对svn 1.5的一次升级。这次升级并不是简单的后台升级,界面也变得似乎更加合理,尤其是commit/revert的对话框文件列表采用了树状结构。然而看起来cool的界面却是中看不中用,实际commit或revert的时候你就会发现,checkbox没了!!!!如果有不想提交的本地文件,我唯一发现的办法就是选中后右键点击,选择“remove from view”。

    这真是subclipse的一大败笔。当看到NetBeans看似完美的svn集成却用了那么烂的选择方式时,非常失望。而subclipse却朝着这个方向迈了一大步。不过似乎在文件或目录前面加上丢失的checkbox就可以挽救这个失误,不知道subclipse的开发组会不会这么做。

    关于这个对话框的不方便,有人在邮件列表里也提出来了,说现在只好用tortoiseSVN或者命令行来做提交。另外用户们还在邮件列表上提出了许多其它的bug,而我在使用的过程中也不时地看到subclipse抛出的exception.

    svn客户端升级到1.5会改变working copy配置文件的结构,所以我还得排除万难降级到subclipse 1.2。而eclipse也用回了稳定的3.3,不知道在NetBeans的压力下匆忙发布的3.4版本里还隐藏着什么陷阱。

  • org.apache.commons.lang.StringUtils 字符串处理

    许多开发人员在做项目时都习惯把一些实用方法做成通用的静态方法,放在一起使用,例如对字符串的处理。有些实际编码中十分常用的方法却没有在Java的String类中提供。实际上Apache的commons子项目的lang包就是帮我们解决这个问题的,比如org.apache.commons.lang.StringUtils这个字符串处理的类。

    在发现它之前我自己已经有了一个StringUtil类,而粗略看了几眼它的文档,我的所有方法都在org.apache.commons.lang.StringUtils类中都有,并且考虑得更加周全。

    例如split方法,在Java中,用逗号分隔一个字符串”a,,b,c,”,结果是一个长度为5的数组:

    [“a”, “”, “b”, “c”, “”]

    而大部分情况下,那些空字符串是不想要的,于是在循环中我们就需要一一判断字符串是不是空的。StringUtils.split方法就帮我们按照这种思路来分割字符串。

    许多开发人员抱怨Java没有提供一个join方法,StringUtils.join解决了这个问题。

    你怎么判断用户的输入是不是空?如果做完整的验证,应该是:

    if (input != null && input.trim().length > 0) {…}

    StringUtils.isBlank方法为我们做这样的判断,它会正确处理null和全部空格的情况。

    StringUtils类还有许多其它的实用方法,可以适应我们日常编程中的大部分字符串处理。另外commons-lang还提供了一个StringEscapeUtils类,可以帮助你防止SQL injection,在HTML中正常输出用户输入的尖括号等。

    虽然我没有仔细看过源代码,不过作为通用的方法,commons-lang对性能的考虑应该比较周到,这一点可以放心。

  • 在KeePass中使用PuTTY

    KeePass是一个开源的密码管理器,我一直在用它保存一些毫无规律的密码。它自己生成随机密码的功能也非常完善。

    PuTTY是一个小巧、免费并且好用的ssh客户端。secureCRT我也用过,不过不太习惯。唯一觉得PuTTY不好的地方是不能保存密码自动登录。

    在KeePass的帮助里有这样的说明

    if you globally (i.e. using the Windows Explorer) register PuTTY for ssh:// URLs, KeePass will automatically use PuTTY for ssh:// URLs

    然而我不太清楚怎么这样做(Windows是很博大精深的哦)。

    不过最后总算折腾出个办法来,可以在KeePass中启动PuTTY并自动输入用户名:

    1. 把PuTTY可执行文件放在KeePass的目录里,即和KeePass可执行文件放在一起
    2. 在KeePass的Options->Setup选项页面,勾选”Use PuTTY for ssh: and telnet: URLs”
    3. 在ssh的密码项中填写用户名,url的格式为ssh://your.tld
    4. 在列表的ssh url上双击即可启动PuTTY打开该ssh站点,并且用户名已经自动填好。或者也可以单击该行后,在窗口下方的详细信息那里点击url
    5. 双击密码项复制密码,粘贴到PuTTY中即可

    唯一不知道的是如何让它自动填写密码然后登录,不过我已经知足了。