【Maven】Maven Plugin示例:自己动手编写Maven插件

需求:

在Maven编译项目的时候,统计代码量,即项目中的文件数目、代码行数,包括java文件和配置文件两种;其中配置文件(sql、xml、properties)代码行数/4 处理。


创建项目:

首先确保已安装m2eclipse插件:http://eclipse.org/m2e/

eclipse - new - Maven Project,选择archetype = maven-archetype-plugin:(或用命令 mvn archetype:generate,再按提示操作)

【Maven】Maven Plugin示例:自己动手编写Maven插件_第1张图片

下一步设置好坐标信息,即可创建一个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.csdn.net/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
最后一个冒号后面,就是上面定义的@goal。

显然这个命令太长了,使用很不方便,可在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>
再在该项目上运行mvn statis:count时就只会统计properties文件了,并将代码量*1.5。

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>





你可能感兴趣的:(【Maven】Maven Plugin示例:自己动手编写Maven插件)