三个 DAL 相关的Java代码小工具

  最近在做 DAL (Data Access Layer 数据访问层) 的服务化,发现有不少地方是人工编写比较繁琐的,因此写了几个小工具来完成。

  

  1.  从 DAO 类自动生成 CoreService 类, CoreService 直接调用 DAO 类

  思路: 通过正则表达式解析方法参数, 使用正则替换及源 DAO 文件来生成 CoreService 源文件。

package zzz.study.utils;

import cc.lovesq.dao.CreativeDAO;

import java.util.List;
import static zzz.study.utils.BaseTool.*;

public class AutoGenerateCoreService {

    public static void main(String[] args) {
        testParseMethod();
        generateCoreServiceFile(CreativeDAO.class);

    }

    public static void generateCoreServiceFile(Class<?> daocls) {

        String daoClassName = daocls.getSimpleName();
        String packageName = daocls.getPackage().getName();
        String daoRelativePath = "/" + packageName.replaceAll("\\.", "/");
        String daoFileName = ALLIN_PROJ_PATH_SRC + "/" + daoRelativePath +
                                                     "/" + daocls.getSimpleName() + ".java";
        String bizType = getBizType(packageName);

        String writeFilename = ALLIN_PROJ_PATH_SRC + "/cc/lovesq/service/" + daoClassName.replace("DAO", "CoreService") + ".java";
        String serviceClassName = daoClassName.replace("DAO", "CoreService");
        String daoRefName = firstToLower(daoClassName);

        List<String> lines = readLines(daoFileName);
        String fileContents = "";
        boolean daoFlag = false;
        for (String line: lines) {

            if (daoFlag) {
                fileContents += "\n\[email protected]\n";
                fileContents += "\tprivate " + daoClassName + " " + daoRefName + ";\n\n";
                daoFlag = false;
            }
            else if (line.contains("interface")) {
                fileContents += "@Component\npublic class " + serviceClassName + " { \n";
                daoFlag = true;
            }
            else if (line.contains(";")) {
                if (!line.contains("import") && !line.contains("package")) {
                    System.out.println(line);
                    System.out.println("parsed: " + parseMethod(line));
                    List<String> parsed = transform(parseMethod(line));
                    String replaceStr = " {\n\t\treturn " + daoRefName + "." + parsed.get(0) + "(" + parsed.get(1) + ");\n\t}\n";
                    String accessQualifier = "";
                    if (!line.contains("public")) {
                        accessQualifier = "public ";
                    }
                    fileContents += "\t" + accessQualifier + " " + line.trim().replace(";", replaceStr);
                }
                else if (line.contains("package")) {
                    System.out.println(line);
                    fileContents += line.replace("dao", "service") + "\n\n";
                    fileContents += "import " + daocls.getPackage().getName() + "." + daoClassName + ";\n";
                    fileContents += "import javax.annotation.Resource;\n" +
                                    "import org.springframework.stereotype.Component;\n" +
                                    "import java.util.List;";
                }
                else {
                    fileContents += line + "\n";
                }
            }
            else {
                fileContents += line + "\n";
            }
        }

        writeFile(writeFilename, fileContents);
    }

}

  2.  根据对应数据库的 DO 类生成业务 Model 类及转换类 DataTransfer

思路: 模板、反射、正则替换

package zzz.study.utils;

import cc.lovesq.pojo.CreativeDO;

import java.lang.reflect.Field;

import static zzz.study.utils.BaseTool.*;

/**
 * Created by shuqin on 16/5/3.
 */
public class AutoGenerateDataTransfer {

    private static final String dataTransferTpl =
            "package cc.lovesq.transfer;\n" +
            "\n" +
            "import cc.lovesq.model.$modelClsName;\n" +
            "import cc.lovesq.pojo.$doClsName;\n\n" +
            "public class $modelClsNameDataTransfer { \n\n" +
            indent(4) + "$doToModelMethod\n\n" +
            indent(4) + "$modelToDOMethod\n\n" +
            "}";

    private static final String doToModelTpl =
            "public static $modelClsName transfer2TO($doClsName $doInstName) {\n" +
            indent(8) + "if ($doInstName == null) {\n" +
            indent(12) + "return null;\n"               +
            indent(8) + "}\n"                          +
            "\n"                               +
            indent(8) + "$modelClsName $modelInstName = new $modelClsName();\n" +
            indent(0) + "$setStatement\n"              +
            indent(8) + "return $modelInstName;\n"     +
            indent(4) + "}" ;

    private static final String modelToDOTpl =
            "public static $doClsName transfer2DO($modelClsName $modelInstName) {\n" +
            indent(8) + "if ($modelInstName == null) {\n" +
            indent(12) + "return null;\n" +
            indent(8) + "}\n" +
            "\n" +
            indent(8) + "$doClsName $doInstName = new $doClsName();\n" +
            indent(0) + "$setStatement\n"              +
            indent(8) + "return $doInstName;\n" +
            indent(4) + "}";

    public static void main(String[] args) {

        autoGenDataTransfer(CreativeDO.class);
    }

    /**
     * 根据 DO 类自动生成 Model 类以及 DataTransfer 类
     * @param doCls DO 类
     */
    public static void autoGenDataTransfer(Class<?> doCls) {

        String doClassName = doCls.getSimpleName();
        Field[] fields = doCls.getDeclaredFields();
        String doInstName = firstToLower(doClassName);
        String modelInstName = strip(doInstName, "DO");
        String modelClsName = firstToUpper(modelInstName);
        StringBuilder setStatementAll = new StringBuilder();
        for (Field f: fields) {
            String fieldName = f.getName();
            String setStatement = String.format("%s%s.set%s(%s.get%s());\n", indent(8),
                    modelInstName, firstToUpper(fieldName), doInstName, firstToUpper(fieldName));
            setStatementAll.append(setStatement);
        }
        String setStatementAllStr = setStatementAll.toString();
        String do2modelMethod = doToModelTpl.replaceAll("\\$modelClsName", modelClsName)
                    .replaceAll("\\$modelInstName", modelInstName)
                    .replaceAll("\\$doClsName", doClassName)
                    .replaceAll("\\$doInstName", doInstName)
                    .replaceAll("\\$setStatement", setStatementAllStr);

        StringBuilder setStatementAllBuilder2 = new StringBuilder();
        for (Field f: fields) {
            String fieldName = f.getName();
            String setStatement = String.format("%s%s.set%s(%s.get%s());\n", indent(8),
                    doInstName, firstToUpper(fieldName), modelInstName, firstToUpper(fieldName));
            setStatementAllBuilder2.append(setStatement);
        }
        String setStatementAll2 = setStatementAllBuilder2.toString();
        String model2doMethod = modelToDOTpl.replaceAll("\\$modelClsName", modelClsName)
                .replaceAll("\\$modelInstName", modelInstName)
                .replaceAll("\\$doClsName", doClassName)
                .replaceAll("\\$doInstName", doInstName)
                .replaceAll("\\$setStatement", setStatementAll2);

        String packageName = doCls.getPackage().getName();
        String bizType = getBizType(packageName);
        String dataTransferClassContent = dataTransferTpl.replaceAll("\\$modelClsName", modelClsName)
                    .replaceAll("\\$doClsName", doClassName)
                    .replaceAll("\\$bizType", bizType)
                    .replaceAll("\\$doToModelMethod", do2modelMethod)
                    .replaceAll("\\$modelToDOMethod", model2doMethod);

        //System.out.println(dataTransferClassContent);

        String doClsRelativePath = "/cc/lovesq/pojo/" + doClassName + ".java";
        String doClsPath = ALLIN_PROJ_PATH_SRC + doClsRelativePath;
        String doClsContent = readFile(doClsPath);

        System.out.println(doClsContent);
        String modelClsContent = doClsContent.replace(doClassName, modelClsName)
                                   .replace("pojo", "model");

        String modelClsRelativePath = "/cc/lovesq/model/" + modelClsName + ".java";
        String modelClsPath = ALLIN_PROJ_PATH_SRC + modelClsRelativePath;
        writeFile(modelClsPath, modelClsContent);

        String transferRelativePath = "/cc/lovesq/transfer";
        String qualifiedPath = ALLIN_PROJ_PATH_SRC + transferRelativePath + "/";
        System.out.println(dataTransferClassContent);
        String writeFilePath = qualifiedPath + modelClsName + "DataTransfer.java";
        System.out.println("Write: " + writeFilePath);
        writeFile(writeFilePath, dataTransferClassContent);
    }

}

3.  移除指定类的 Javadoc 注释

  知识点: 多行的正则匹配, 正则替换; 非贪婪匹配。 注意到  (\\\/\*.*?\\*\\/) 里面有个问号, 如果没有这个问号, 这个正则会从第一个 /* 匹配到最后一个 */ ,相当于整个文件都匹配进去了,显然不是期望的。加上问号后,该正则是非贪婪匹配,每次只要匹配到 */ 就会结束此次匹配。

package zzz.study.utils;

import cc.lovesq.service.CreativeService;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static zzz.study.utils.BaseTool.*;

/**
 * 移除指定类的 Javadoc 注释
 * Created by shuqin on 16/5/4.
 */
public class RemoveJavadocComments {

    private static final String javadocRegexStr = "(\\\/\*.*?\\*\\/)";
private static final Pattern javadocPattern = Pattern.compile(javadocRegexStr, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);

    public static void main(String[] args) {

        // 移除指定包下面的类 Javadoc 注释
        String tradeDALPackage = ALLIN_PROJ_PATH_SRC + "/cc/lovesq/controller";
        List<Class> classes = getClasses(tradeDALPackage);
        for (Class c: classes) {
            if (c.getSimpleName().endsWith("Controller")) {
                removeJavadoc(c);
            }
        }

        // 移除单个类的 Javadoc 注释
        removeJavadoc(CreativeService.class);
    }

    public static void removeJavadoc(Class<?> coreServiceCls) {

        String coreServiceName = coreServiceCls.getSimpleName();
        String packageName = coreServiceCls.getPackage().getName();
        String packagePath = "/" + packageName.replaceAll("\\.", "/");

        String coreServiceClsRelativePath = packagePath + "/" + coreServiceName + ".java";
        String coreServiceClsPath = ALLIN_PROJ_PATH_SRC + coreServiceClsRelativePath;
        String coreServiceContent = readFile(coreServiceClsPath);

        Matcher m = javadocPattern.matcher(coreServiceContent);
        String newContent = coreServiceContent;
        while(m.find()) {
            String matchedJavadoc = coreServiceContent.substring(m.start(), m.end());
            newContent = newContent.replace(matchedJavadoc, "");
        }
        newContent = newContent.replaceAll("\n\\s*\n", "\n\n");
        writeFile(coreServiceClsPath, newContent);
    }

}

基本工具类:

  

package zzz.study.utils;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by shuqin on 16/5/4.
 */
public class BaseTool {

    public static final String ALLIN_PROJ_PATH = System.getProperty("user.dir");
    public static final String ALLIN_PROJ_PATH_SRC = ALLIN_PROJ_PATH + "/src/main/java";

    public static String strip(String origin, String toStrip) {
        int index = origin.indexOf(toStrip);
        if (index == -1) {
            return origin;
        }
        else {
            return origin.substring(0, index);
        }
    }

    public static String firstToLower(String doClassName) {
        return "" + String.valueOf(doClassName.charAt(0)).toLowerCase() + doClassName.substring(1);
    }

    public static String firstToUpper(String fieldName) {
        return "" + String.valueOf(fieldName.charAt(0)).toUpperCase() + fieldName.substring(1);
    }

    public static String getBizType(String packageName) {
        int preIndex = packageName.indexOf("common.");
        if (preIndex == -1) {
            return "";
        }
        String bizPart = packageName.substring(preIndex+"common.".length());
        int bizIndex = bizPart.indexOf(".");
        if (bizIndex == -1) {
            return "";
        }
        return bizPart.substring(0, bizIndex);
    }

    public static String indent(int n) {
        StringBuilder spaces = new StringBuilder();
        for (int i=0; i<n; i++) {
            spaces.append(‘ ‘);
        }
        return spaces.toString();
    }

    public static String readFile(String filePath) {
        String fileContent = "";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(new File(filePath)));
            StringBuilder doClsContentBuilder = new StringBuilder();
            String line = "";
            while ((line = reader.readLine()) != null) {
                doClsContentBuilder.append(line + "\n");
            }
            fileContent = doClsContentBuilder.toString();
        } catch (IOException ioe) {
            System.err.println("Failed to Read File " + filePath + " : " + ioe.getMessage());
        }
        return fileContent;
    }

    public static List<String> readLines(String filePath) {
        List<String> lines = new ArrayList<String>();
        try {
            BufferedReader reader = new BufferedReader(new FileReader(new File(filePath)));
            String line = "";
            while ((line = reader.readLine()) != null) {
                lines.add(line);
            }
        } catch (IOException ioe) {
            System.err.println("Failed to Read File " + filePath + " : " + ioe.getMessage());
        }
        return lines;
    }

    public static void writeFile(String filePath, String fileContent) {
        try {
            BufferedWriter modelFileW = new BufferedWriter(new FileWriter(new File(filePath)));
            modelFileW.write(fileContent);
            modelFileW.close();
        } catch (IOException ioe) {
            System.err.println("Failed to write Java File " + filePath + " : " + ioe.getMessage());
        }
    }

    public static List<String> fetchAllFiles(String path) {
        List<String> fetchedFiles = new ArrayList<String>();
        fetchFiles(path, fetchedFiles);
        return fetchedFiles;
    }

    public static void fetchFiles(String path, List<String> fetchedFiles) {
        File[] dirAndfiles = (new File(path)).listFiles();
        if (dirAndfiles!=null && dirAndfiles.length > 0) {
            for (File file: dirAndfiles) {
                if (file.isFile()) {
                    fetchedFiles.add(file.getAbsolutePath());
                }
            }

            for (File file: dirAndfiles) {
                if (file.isDirectory()) {
                    fetchFiles(file.getAbsolutePath(), fetchedFiles);
                }
            }
        }
    }

    public static List<Class> getClasses(String path) {
        List<String> files = fetchAllFiles(path);
        List<Class> result = new ArrayList<Class>();
        ClassLoader cld = Thread.currentThread().getContextClassLoader();
        for (String fname: files) {
            String fn = fname.replace(ALLIN_PROJ_PATH_SRC + "/", "").replace(".java", "");
            String qualifiedClassName = fn.replaceAll("/", ".");
            try {
                Class<?> cls = cld.loadClass(qualifiedClassName);
                result.add(cls);
            } catch (ClassNotFoundException cnfe) {
                System.err.println("Failed to load class " + qualifiedClassName + " : " + cnfe.getMessage());
            }

        }
        return result;
    }

    public static final String methodNameRegexStr = "\\s*(?:\\w+\\s+)?\\w+<?\\w+>?\\s+(\\w+)";
    public static final String singleParamRegexStr = "[^,]*\\w+<?\\w+>?\\s+(\\w+)\\s*";
    public static final String simpleMethodSignRexStr = methodNameRegexStr + "\\(" + singleParamRegexStr + "\\)\\s*;\\s*";
    public static final String twoParamMethodSignRegStr = methodNameRegexStr + "\\(" + singleParamRegexStr + "," + singleParamRegexStr + "\\);\\s*";
    //val generalParamMethodSignRegStr = methodNameRegexStr + "\\((" + singleParamRegexStr + "(?:," + singleParamRegexStr + ")*)\\);\\s*";
    public static final String generalParamMethodSignRegStr = methodNameRegexStr + "\\((.*)\\);\\s*";

    public static final Pattern singleParamPattern = Pattern.compile(singleParamRegexStr);
    public static final Pattern generalParamMethodSignPattern = Pattern.compile(generalParamMethodSignRegStr);

    /**
     * 从方法签名中解析出方法名称\参数列表
     * @param methodSign 方法签名
     * @return ["方法名称", "参数1, 参数2, ..., 参数N"]
     */
    public static List<String> parseMethod(String methodSign) {

        Matcher m = generalParamMethodSignPattern.matcher(methodSign);
        String methodName = "";
        String args = "";
        List<String> parsed = new ArrayList<String>();

        if (m.find()) {
            methodName = m.group(1);
            args = m.group(2);
        }
        else {
            return Arrays.asList(new String[]{"", ""});
        }
        parsed.add(methodName);
        String[] params = args.split(",");
        for (String param: params) {
            String arg = extractArgName(param);
            parsed.add(arg);
        }
        return parsed;
    }

    public static String extractArgName(String singleParam) {
        Matcher m = singleParamPattern.matcher(singleParam);
        return m.find() ? m.group(1) : "";
    }

    public static List<String> transform(List<String> parsed) {
        if (parsed == null || parsed.isEmpty()) {
            return parsed;
        }
        List<String> result = new ArrayList<String>();
        result.add(parsed.get(0));
        if (parsed.size() == 2) {
            result.add(parsed.get(1));
        }
        else {
            int size = parsed.size();
            StringBuilder argBuilder = new StringBuilder();
            for (int i=1; i< size-1; i++) {
                argBuilder.append(parsed.get(i) + ", ");
            }
            argBuilder.append(parsed.get(size-1));
            result.add(argBuilder.toString());
        }
        return result;
    }

    public static void testParseMethod() {
        Map<String,List<String>> testMethods = new HashMap<String, List<String>>();
        testMethods.put(" List<OrderDO> queryOrder(int kdtId); ", Arrays.asList(new String[]{"queryOrder", "kdtId"}));
        testMethods.put(" List<OrderDO> queryOrder( int kdtId ); ", Arrays.asList(new String[]{"queryOrder", "kdtId"}));
        testMethods.put(" OrderDO queryOrder(@Param(\"kdtId\") int kdtId); ", Arrays.asList(new String[]{"queryOrder", "kdtId"}));
        testMethods.put(" List<OrderDO> queryOrder(List<String> orderNos); " , Arrays.asList(new String[]{"queryOrder", "orderNos"}));
        testMethods.put(" List<OrderDO> queryOrder(@Param(\"orderNos\") List<String> orderNos); ", Arrays.asList(new String[]{"queryOrder", "orderNos"}));
        testMethods.put(" OrderDO queryOrder(String orderNo, Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNo, kdtId"}));
        testMethods.put(" OrderDO queryOrder(String orderNo, @Param(\"kdtId\") Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNo, kdtId"}));
        testMethods.put(" OrderDO queryOrder(@Param(\"orderNo\") String orderNo, Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNo, kdtId"}));
        testMethods.put(" OrderDO queryOrder(@Param(\"orderNo\") String orderNo, @Param(\"kdtId\") Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNo, kdtId"}));
        testMethods.put(" OrderDO queryOrder(List<String> orderNos, Integer kdtId); \n", Arrays.asList(new String[]{"queryOrder", "orderNos, kdtId"}));
        testMethods.put(" OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNos, kdtId"}));
        testMethods.put(" OrderDO queryOrder(List<String> orderNos, @Param(\"kdtId\") Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNos, kdtId"}));
        testMethods.put(" OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, @Param(\"kdtId\") Integer kdtId); ", Arrays.asList(new String[]{"queryOrder", "orderNos, kdtId"}));
        testMethods.put(" OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, @Param(\"page\") Integer page, @Param(\"pageSize\") Integer pageSize); ", Arrays.asList(new String[]{"queryOrder", "orderNos, page, pageSize"}));

        Set<Map.Entry<String, List<String>>> entries = testMethods.entrySet();
        for (Map.Entry entry: entries) {
            String methodSign = (String)entry.getKey();
            List<String> expected = (List<String>)entry.getValue();
            List<String> actual = transform(parseMethod(methodSign));
            if (!assertListEqual(actual, expected)) {
                System.err.println("failed: " + methodSign);
                System.err.println("expected: " + expected);
                System.err.println("actual: " + actual);
            }
        }

        System.out.println("Test ParseMethod passed");

    }

    public static boolean assertListEqual(List<String> list1, List<String> list2) {
        if (list1 == null && list2 == null) {
            return true;
        }
        if ((list1 == null && list2 !=null) || (list1 != null && list2 ==null)) {
            return false;
        }
        if (list1.size() != list2.size()) {
            return false;
        }
        for (int i=0; i< list1.size(); i++) {
            if (!list1.get(i).equals(list2.get(i))) {
                return false;
            }
        }
        return true;
    }

}

小结:

   任何繁琐容易出错的技术含量不高的活,都可以转化为技术含量"相对有挑战"的活, 就看方案与思路。只要看上去比较有规律的事情, 通常是可以自动化地完成的, 包括生成源代码文件。 方案与思路总体上决定了解决质量的高度。正则很强大!

时间: 2024-09-28 16:37:49

三个 DAL 相关的Java代码小工具的相关文章

JS-在线运行代码小工具

原理:window.open()方法,open一个新的空白页,然后把文本框中粘贴的代码通过DOM操作,写到新的代码页中, 再利用document.write的功能(写进去之前把其他的全部删掉,并且写进去的html代码是可以解析的.)完成想要的效果. window.open打开的新页面也是一个浏览器对象,也具有document.write这个方法:. 1 <!DOCTYPE html> 2 <html> 3 <!-- 4 作者:[email protected] 5 时间:20

Android Layout Binder(在线将XML中View find出来,生成java代码的工具)

废话不多说,这是地址:http://android.lineten.net/layout.php. 有图有真相,例如: 你的XML假如是这样: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fil

Java代码检测工具

开发软件时,我的主要目标之一是:要么防止将缺陷引入代码库,要么限制缺陷的生存期:换言之,要尽早找到缺陷.很显然,越是了解如何编写更好的代码以及如何有效测试软件,就越能及早地捕捉到缺陷.我也很想要一张能发现潜在缺陷的安全之网. 得出了这样的结论:将检验工具集成到构建过程(例如,使用 Ant 或 Maven)中,能够建立起一种寻找潜在缺陷的方法.尽管这种方法使一致性成为可能并超越了 IDE,但它也有一点反作用.必须在本地构建软件或等待 Continuous Integration 构建的运行.如果使

阿里java代码检测工具p3c

阿里在杭州云栖大会上,正式发布众所期待的<阿里巴巴Java开发规约>扫描插件!该插件由阿里巴巴P3C项目组研发.这个项目组是阿里巴巴开发爱好者自发组织形成的虚拟项目组,把<阿里巴巴Java开发规约>强制条目转化成自动化插件,并实现部分的自动编程.插件的下载地址:https://github.com/alibaba/p3c 或者在Github直接搜索p3c插件有哪些功能?为了让开发者更加方便.快速将规范推动并实行起来,阿里基于手册内容,研发了一套自动化的IDE检测插件(IDEA.Ec

java 代码分析工具——JDepend

最近学习Mybatis的官方文档,看到了[项目文档]一节有很多内容没有见过,做个笔记,理解一下. 百科上的介绍,我竟然都看懂了,那就不找其他地方的资料了. JDepend 一个开放源代码的可以用来评价Java程序质量的优秀工具(定义),它遍历Java class的文件目录,以Java包(package)为单位,为每一个包/类自动生成 包的依赖程度(怎么做的),稳定性,可靠度等的评价报告,根据这些报告,我们可以得到包或类之间的依赖关系,并分析出包的稳定程度,抽象程度,是否存在循环依赖关系 等(用途

Java 实现小工具读取文件有多少个单词

在windows 下执行如下 读取文件单词的代码在另一篇博客里传送 写完工具类后,在网上找到各种打包jar包的教程,打包好我们的读单词的代码,再新建一个记事本,名字自己命名.bat的批处理文件,类容如下: path是安装的jdk的路径,Test.jar是代码打包的包名 @echo on @echo startup set path=C:\Program Files\Java\jdk1.8.0_201\bin java -jar Test.jar pause 两个文件放在一个文件夹下,双击star

44个JAVA代码质量管理工具(转)

1. CodePro AnalytixIt’s a great tool (Eclipse plugin) for improving software quality. It has the next key features: Code Analysis, JUnit Test Generation, JUnit Test Editor, Similar Code Analysis, Metrics, Code Coverage and Dependency Analysis.2. PMDI

EMMA: 免费java代码测试覆盖工具

From:http://emma.sourceforge.net/ EMMA: a free Java code coverage tool   Code coverage for free: a basic freedom?         Until recently, the world of Java development had been plagued by an absurd discrepancy: Java developers had excellent free IDEs

java 编写小工具 尝试 学习(五)

1.今天 学习 标签 的 控件 的使用 ,学习 视频教程 参考  :http://edu.51cto.com/lesson/id-17733.html 常用控件如下截图: