需求:
在Maven编译项目的时候,统计代码量,即项目中的文件数目、代码行数,包括java文件和配置文件两种;其中配置文件(sql、xml、properties)代码行数/4 处理。
创建项目:
首先确保已安装m2eclipse插件:http://eclipse.org/m2e/
eclipse - new - Maven Project,选择archetype = maven-archetype-plugin:(或用命令 mvn archetype:generate,再按提示操作)
下一步设置好坐标信息,即可创建一个maven plugin工程。
生成的pom.xml文件如下(有手工改动,参见注释):
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.alpha.wang</groupId>
- <artifactId>maven-statis-plugin</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>maven-plugin</packaging>
- <name>alpha-statis-plugin Maven Plugin</name>
- <url>http://blog.youkuaiyun.com/vking_wang</url>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.apache.maven</groupId>
- <artifactId>maven-plugin-api</artifactId>
- <version>2.0</version>
- </dependency>
- <!-- 否则Mojo中的org.apache.maven.model.Resource,无法解析 -->
- <dependency>
- <groupId>org.apache.maven</groupId>
- <artifactId>maven-model</artifactId>
- <version>2.2.1</version>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>3.8.1</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-plugin-plugin</artifactId>
- <version>2.5.1</version>
- <configuration>
- <!--[WARNING]Goal prefix is specified as: 'maven-statis-plugin'. Maven currently expects it to be 'statis'.-->
- <!-- goalPrefix>maven-statis-plugin</goalPrefix-->
- <goalPrefix>statis</goalPrefix>
- </configuration>
- <executions>
- <execution>
- <id>generated-helpmojo</id>
- <goals>
- <goal>helpmojo</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <!-- generics are not supported in -source 1.3 (use -source 5 or higher to enable generics)-->
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.5.1</version>
- <configuration>
- <source>1.5</source>
- <target>1.5</target>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </project>
注意packing为maven-plugin;并有对maven-plugin-api的依赖。
还会自动生成一个public class MyMojo extends AbstractMojo的源文件。
编写Mojo
Mojo = Maven Old Java Object,需要继承AbstractMojo,并实现其execute方法。
上一步已经自动生成了一个Mojo,我们删掉重新创建一个:
- import org.apache.maven.model.Resource;
- import org.apache.maven.plugin.AbstractMojo;
- import org.apache.maven.plugin.MojoExecutionException;
- /**
- *
- * @goal count
- *
- * @phase process-sources
- */
- public class CountMojo extends AbstractMojo
- {
- private static final String[] INCLUDES_DEFAULT = {"java", "xml", "sql", "properties"};
- private static final String[] RATIOS_DEFAULT = {"1.0", "0.25", "0.25", "0.25"};
- private static final String DOT = ".";
- /**
- * @parameter expression="${project.basedir}"
- * @required
- * @readonly
- */
- private File basedir;
- /**
- * @parameter expression="${project.build.sourceDirectory}"
- * @required
- * @readonly
- */
- private File sourcedir;
- /**
- * @parameter expression="${project.build.testSourceDirectory}"
- * @required
- * @readonly
- */
- private File testSourcedir;
- /**
- * @parameter expression="${project.resources}"
- * @required
- * @readonly
- */
- private List<Resource> resources;
- //private List<File> resources;
- /**
- * @parameter expression="${project.testResources}"
- * @required
- * @readonly
- */
- private List<Resource> testResources;
- //private List<File> testResources;
- /**
- * @parameter
- */
- private String[] includes;
- /**
- * @parameter
- */
- private String[] ratios;//TODO 定义为double[],从xml读取时提示java.lang.ClassCastException: [D cannot be cast to [Ljava.lang.Object;
- private Map<String, Double> ratioMap = new HashMap<String, Double>();
- private long realTotal;
- private long fakeTotal;
- public void execute() throws MojoExecutionException
- {
- initRatioMap();
- try{
- countDir(sourcedir);
- countDir(testSourcedir);
- for(Resource res : resources){
- countDir(new File(res.getDirectory()));
- }
- for(Resource res : testResources){
- countDir(new File(res.getDirectory()));
- }
- getLog().info("TOTAL LINES:"+fakeTotal+ " ("+realTotal+")");
- }catch (IOException e){
- throw new MojoExecutionException("Unable to count lines of code", e);
- }
- }
- }
所调用的工具方法定义如下:
- private void initRatioMap() throws MojoExecutionException{
- if(includes == null || includes.length == 0){
- includes = INCLUDES_DEFAULT;
- ratios = RATIOS_DEFAULT;
- }
- if(ratios == null || ratios.length == 0){
- ratios = new String[includes.length];
- for(int i=0; i<includes.length; i++){
- ratios[i] = "1.0";
- }
- }
- if(includes.length != ratios.length){
- throw new MojoExecutionException("pom.xml error: the length of includes is inconsistent with ratios!");
- }
- ratioMap.clear();
- for(int i=0; i<includes.length; i++){
- ratioMap.put(includes[i].toLowerCase(), Double.parseDouble(ratios[i]));
- }
- }
- private void countDir(File dir) throws IOException {
- if(! dir.exists()){
- return;
- }
- List<File> collected = new ArrayList<File>();
- collectFiles(collected, dir);
- int realLine = 0;
- int fakeLine = 0;
- for(File file : collected){
- int[] line = countLine(file);
- realLine += line[0];
- fakeLine += line[1];
- }
- String path = dir.getAbsolutePath().substring(basedir.getAbsolutePath().length());
- StringBuilder info = new StringBuilder().append(path).append(" : ").append(fakeLine).append(" ("+realLine+")")
- .append(" lines of code in ").append(collected.size()).append(" files");
- getLog().info(info.toString());
- }
- private void collectFiles(List<File> collected, File file)
- throws IOException{
- if(file.isFile()){
- if(isFileTypeInclude(file)){
- collected.add(file);
- }
- }else{
- for(File files : file.listFiles()){
- collectFiles(collected, files);
- }
- }
- }
- private int[] countLine(File file)
- throws IOException{
- BufferedReader reader = new BufferedReader(new FileReader(file));
- int realLine = 0;
- try{
- while(reader.ready()){
- reader.readLine();
- realLine ++;
- }
- }finally{
- reader.close();
- }
- int fakeLine = (int) (realLine * getRatio(file));
- realTotal += realLine;
- fakeTotal += fakeLine;
- StringBuilder info = new StringBuilder().append(file.getName()).append(" : ").append(fakeLine).append(" ("+realLine+")")
- .append(" lines");
- getLog().debug(info.toString());
- return new int[]{realLine, fakeLine};
- }
- private double getRatio(File file){
- double ratio = 1.0;
- String type = getFileType(file);
- if(ratioMap.containsKey(type)){
- ratio = ratioMap.get(type);
- }
- return ratio;
- }
- private boolean isFileTypeInclude(File file){
- boolean result = false;
- String fileType = getFileType(file);
- if(fileType != null && ratioMap.keySet().contains(fileType.toLowerCase())){
- result = true;
- }
- return result;
- }
- private String getFileType(File file){
- String result = null;
- String fname = file.getName();
- int index = fname.lastIndexOf(DOT);
- if(index > 0){
- String type = fname.substring(index+1);
- result = type.toLowerCase();
- }
- return result;
- }
注意此处 annotation 的使用,例如@parameter expression="${project.build.sourceDirectory}"就会自动将项目源代码路径赋值给sourcedir变量。
特别注意@goal,每个插件可有多个目标,在用mvn命令时需要用到这个goal。
执行
可使用mvn clean install将自定义的这个插件安装到本地仓库。
- mvn clean install
- mvn com.alpha.wang:maven-statis-plugin:0.0.1-SNAPSHOT:count
显然这个命令太长了,使用很不方便,可在settings.xml中配置如下:
- <pluginGroups>
- <pluginGroup>com.alpha.wang</pluginGroup>
- </pluginGroups>
- mvn statis:count
提供配置点
Mojo的includes、ratios变量标记为@parameter,表示用户可在pom.xml中配置该字段;例如可在目标项目的pom.xml中增加如下内容:
- <build>
- <plugins>
- <plugin>
- <groupId>com.alpha.wang</groupId>
- <artifactId>maven-statis-plugin</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <configuration>
- <includes>
- <!--include>java</include-->
- <include>properties</include>
- </includes>
- <ratios>
- <ratio>1.5</ratio>
- </ratios>
- </configuration>
- </plugin>
D:\>mvn statis:count
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'statis'.
[INFO] ------------------------------------------------------------------------
[INFO] Building com.。。。
[INFO] task-segment: [statis:count]
[INFO] ------------------------------------------------------------------------
[INFO] [statis:count]
[INFO] \src\main\java : 0 (0) lines of code in 0 files
[INFO] \src\main\resources : 18 (12) lines of code in 1 files
[INFO] TOTAL LINES:18 (12)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: < 1 second
[INFO] Finished at: Tue Feb 26 17:11:22 CST 2013
[INFO] Final Memory: 6M/215M
[INFO] ------------------------------------------------------------------------
绑定插件
那么每次都必须手工执行命令来统计代码量吗?我们可以绑定插件到生命周期的某个阶段,例如install阶段,那么只要每次install该项目的时候,就会自动统计代码量。
修改目标项目的pom文件,注意<phase>、<goals>标签:
- <build>
- <plugins>
- <plugin>
- <groupId>com.alpha.wang</groupId>
- <artifactId>maven-statis-plugin</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <configuration>
- <includes>
- <include>java</include>
- <include>properties</include>
- </includes>
- <ratios>
- <ratio>1.0</ratio>
- <ratio>0.5</ratio>
- </ratios>
- </configuration>
- <executions>
- <execution>
- <id>count line number</id>
- <phase>install</phase>
- <goals>
- <goal>count</goal>
- </goals>
- </execution>
- </executions>
- </plugin>