环信技术博客

开源代码应用之Eclipse篇

2015年06月30日|作者: |Java, 开源|标签:, ,

开写这篇的时候,恰逢Eclipse Mars(4.5)正式发布,终于由日蚀变登火星了,也离我开始基于Eclipse开发产品已经过去10年,这10年间,经历了Eclipse由私有核心框架到拥抱OSGi, 由单一Java IDE成长为巨无霸式的技术平台,由纯桌面到Web,嵌入式全面开花,个人也经历了从普通开发者成长为committer,又离开社区的过程,唯一不变的是:Eclipse依然是我开发Java唯一的选择。

对于这样一个由全世界最smart的一群人贡献和维护的开源项目(群),我相信任何热爱这个行业的工程师都能从中获得收益,这次就谈谈我基于Eclipse写的一个小工具。

不知道大家有没有类似的体会,每到产品发布期截止的时候,team就会开始忙乱的整理Java源代码中的license声明问题,严格统一的开发风格对所有的team来讲,基本都是一种奢望,从头开始不可能,那怎么办,不修复吧,不能发布,修复吧,这样的烂活没人愿意干,大概说来,修复Java源代码里面的license声明分为以下两个主流方式:
1. 既然是Java源代码,那就Java上啊,不就读出文件来,插入或替换吗?,定位吗,嗯,文件头的easy,成员变量型的,得想想...
2. 杀鸡焉用牛刀?,组合下Unix里面的小命令,分分钟搞定。

两种方式下的结果我都见过,实话说,的确不怎么样。

这件事情简单吗?说实话不难,但 Oracle依然把Java源代码里的license声明整成下面这个模样,就为了把以前Sun的license声明改成自己的。

oracle

这对很多有代码格式强迫症的工程师来讲,比杀了他们还难受啊。

其实我并没有接到这样的烂活,我只是思考了下,如果要处理好,该怎么办?嗯,这事要搞好,要是能操纵Java源代码的每一个部分不就行了?

哇靠,有人马上会跳起来说,这得懂编译器哪,对,就是编译器,不过也没有那么复杂,也就用了一丁丁点AST知识,不知道AST?哦,哪也没问题,有Eclipse替你做。

于是我开始动手实现这么一个能快速修复Java源代码中license声明的小工具,基本思路是基于Eclipse JDT里的AST实现,在Java语法这个粒度来修改,并做成一个Eclipse Plug-in,这下大家安装后,简单到点个button,就能完成工作。

具体实现步骤如下:

1. 生成一个Eclipse Plug-in项目,选个模版,最简单的那种,能点toolbar上面的button,弹出个"hello, world"对话框就可以。不知道怎么开发一个Eclipse Plug-in啊,没关系,看完这篇blog,你就会了。(别忘了好评!)

2. 在Action的回调方法里面,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void run(IAction action) {  
    license = getLicenseContent(LICENSE_FILE_NAME);  
    license_inline = getLicenseContent(LICENSE_INLINE_FILE_NAME);  
    if (license_inline.endsWith("\n")) {  
        license_inline = license_inline.substring(0, license_inline.length() - 1);  
    }  
    sum = 0;  
 
    IWorkspace workspace = ResourcesPlugin.getWorkspace();  
    IWorkspaceRoot root = workspace.getRoot();  
    IProject[] projects = root.getProjects();  
    for (IProject project : projects) {  
        try {  
            if (project.isOpen()) {  
                processProject(project);  
            }         
        } catch (Exception e) {  
            MessageDialog.openInformation(window.getShell(), "Fix License", "Exception happened, please check the console log.");  
            e.printStackTrace();  
            return;  
        }  
    }  
    MessageDialog.openInformation(window.getShell(), "Fix License", "All java source files have been processed. Total = " + sum);  
}

首先获得license的内容,分为主license和行内license,具体内容这里就不显示了,然后获取Eclipse里面所有的项目,遍历每个项目并处理,这里只处理打开的项目,如果你有不想处理的项目,关闭就行。

3. 处理项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void processProject(IProject project) throws Exception {  
    if (project.isNatureEnabled("org.eclipse.jdt.core.javanature")) {  
        IJavaProject javaProject = JavaCore.create(project);  
        IPackageFragment[] packages = javaProject.getPackageFragments();  
        for (IPackageFragment mypackage : packages) {  
            if (mypackage.getKind() == IPackageFragmentRoot.K_SOURCE) {  
                for (ICompilationUnit unit : mypackage.getCompilationUnits()) {  
                    sum = sum + 1;  
                    processJavaSource(unit);  
                }  
            }  
        }  
    }  
}

当然只修复Java项目,没有Java nature的,一律抛弃。
获得Java项目后,获取所有的package,这里的package和通常意义上Java的package不同,具体意义看API,就当课后作业。
再进一步,就可以获取Java源文件,并取得编译单元,有了这个,以后的路就有方向了。

4. 处理Java源文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void processJavaSource(ICompilationUnit unit) throws Exception {  
    ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager();  
    IPath path = unit.getPath();  
    try {  
        bufferManager.connect(path, null);  
        ITextFileBuffer textFileBuffer = bufferManager.getTextFileBuffer(path);  
        IDocument doc = textFileBuffer.getDocument();  
        if ((license !=null) && (license.length() > 0)) {  
            processHeadLicense(doc);  
        }  
        if ((license_inline != null) && (license_inline.length() > 0)) {  
            processInlineLicense(doc);  
        }  
        textFileBuffer.commit(null, false);  
    } finally {  
        bufferManager.disconnect(path, null);  
    }  
}

这里用到了一些Eclipse Jface text包里面的东西,和Java里面常见的文件读写API有些不一样,但基本思想是一致的。等取到了IDocument对象,就可以开始正式的license处理。

5. 处理Java文件头license声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void processHeadLicense(IDocument doc) throws Exception {  
    CompilationUnit cu = getAST(doc);  
    Comment comment = null;  
    if (cu.getCommentList().size() == 0) {  
        doc.replace(0, 0, license);  
    } else {  
        comment = (Comment)cu.getCommentList().get(0);  
        String firstComment = doc.get().substring(comment.getStartPosition(), comment.getStartPosition() + comment.getLength());  
        if (validateHeadLicense(firstComment)) {  
            doc.replace(comment.getStartPosition(), comment.getLength(), license);  
        } else {  
            doc.replace(0, 0, license);  
        }         
    }  
}  
 
private CompilationUnit getAST(IDocument doc) {  
    ASTParser parser = ASTParser.newParser(AST.JLS4);  
    parser.setKind(ASTParser.K_COMPILATION_UNIT);  
    parser.setSource(doc.get().toCharArray());  
    parser.setResolveBindings(true);  
    CompilationUnit cu = (CompilationUnit) parser.createAST(null);  
 
    return cu;  
}

基于AST就可以得到Java源代码里面的所有comments,接下来就可以根据各种情况插入或替换文件头的license声明。

6. 成员变量型license声明。
这种license声明类似下面这个例子。

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo {  
    public static final String COPYRIGHT = "(C) Copyright IBM Corporation 2013, 2014, 2015.";  
 
    public Demo() {  
 
    }  
 
    public void hello() {  
 
    }  
 
}

它的处理方式如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void processInlineLicense(IDocument doc) throws Exception {  
    CompilationUnit cu = getAST(doc);  
    cu.recordModifications();  
    AST ast = cu.getAST();  
 
    if (cu.types().get(0) instanceof TypeDeclaration) {  
        TypeDeclaration td = (TypeDeclaration)cu.types().get(0);  
        FieldDeclaration[] fd = td.getFields();  
        if (fd.length == 0) {  
            td.bodyDeclarations().add(0, createLiceseInLineField(ast));  
        } else {  
            FieldDeclaration firstFd = fd[0];  
            VariableDeclarationFragment vdf = (VariableDeclarationFragment)firstFd.fragments().get(0);  
            if (vdf.getName().getIdentifier().equals("COPYRIGHT")) {  
                td.bodyDeclarations().remove(0);  
                td.bodyDeclarations().add(0, createLiceseInLineField(ast));  
            } else {  
                td.bodyDeclarations().add(0, createLiceseInLineField(ast));  
            }  
        }             
    }  
 
    //record changes  
    TextEdit edits = cu.rewrite(doc, null);  
    edits.apply(doc);  
}  
 
private FieldDeclaration createLiceseInLineField(AST ast) {  
    VariableDeclarationFragment vdf = ast.newVariableDeclarationFragment();  
    vdf.setName(ast.newSimpleName("COPYRIGHT"));  
    StringLiteral sl = ast.newStringLiteral();  
    sl.setLiteralValue(license_inline);  
    vdf.setInitializer(sl);  
    FieldDeclaration fd = ast.newFieldDeclaration(vdf);  
    fd.modifiers().addAll(ast.newModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL));  
    fd.setType(ast.newSimpleType(ast.newSimpleName("String")));  
 
    return fd;  
}

成员变量类型的license声明处理起来稍显麻烦,主要原因是牵扯到Java成员变量的创建和解析,但其实也不是很难理解,而且从中可以学到AST是如何精细处理Java类的各个组成部分的。

7. 测试。
启动一个新的调试Eclipse Plug-in的Eclipse Runtime,导入任意几个Java项目,从菜单或工具栏上面选择“Fix License” action,完成之后检查任意的Java源文件,看看license是否已经修复。

来看看一个简单的测试结果吧。
这个是修复前的

1
2
3
4
5
6
7
8
9
10
11
12
package com.demo;  
 
public class Demo {  
    public Demo() {  
 
    }  
 
    public void hello() {  
 
    }  
 
}

这个是修复后的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* IBM Confidential 
 * OCO Source Materials 
 *  
 * (C)Copyright IBM Corporation 2013, 2014, 2015. 
 * 
 * The source code for this program is not published or otherwise 
 * divested of its trade secrets, irrespective of what has been 
 * deposited with the U.S. Copyright Office. 
*/  
package com.demo;  
 
public class Demo {  
    public static final String COPYRIGHT = "(C) Copyright IBM Corporation 2013, 2014, 2015.";  
 
    public Demo() {  
 
    }  
 
    public void hello() {  
 
    }  
 
}

8. 打包分发。
这个工具Plug-in可以按Eclipse的标准插件打包并安装,或者生成一个Update Site以供用户在线安装。

好了,啰嗦了这么多,到了该结束的时刻,最后一句,这个小工具所有的源代码已经在GitHub上开源,喜欢可以去下载并测试,源代码里面附有一份详细安装的文档。

工具地址:https://github.com/alexgreenbar/open_tools.git

本文章版权归环信所有,转载请注明出处。更多技术文章请访问http://blog.easemob.com/

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>