你的位置:首页 > Java教程

[Java教程]eclipse 插件编写(四)


前言

前面几篇文章讲了下如果编写简单的eclipse插件,如创建插件项目、编写右键弹出菜单等功能,接下来主要写一下如何生成代码的功能,这一片的功能跟插件本身的编写关联不太大,主要处理插件之后的业务内容。即弹出菜单后,点击后 执行生成代码的功能,由于这一块的功能相对独立,所以单独建一个项目用来管理,以跟插件项目进行解耦,方便后期的维护,由于这一块内容相对较多且引用了其他项目的一些内容,所以简单列举一下内容讲解一下,就不在进行从头进行演示了。后面会附加项目源码,可以自己运行下看看效果。

对了,这里着重提示一下,本项目中引用且参考了其他的项目就是以前一个开源的koala项目,现在应该有一段时间不更新了,不过里面的思想还是很不错的,大家有时间可以去看看。本项目中的一些目录结构及项目代码都参考或引用了该项目的内容。

本片生成代码的核心生成领域对象的Form、Assembler,Form是平时业务中所属的VO,Assembler是领域对象到Form的一个转换的装配器。

引用jar包

image

这里引用的jar包,有一些commons的工具jar包及log4j的日志jar包,其他的还有两个jar包,分别是:

koala-plugin-util-core:这个jar包内容主要是一些基础工具操作,如:文件复制、

velocity:这是一个模板引擎工具,跟freemark相似,具体内容可以上网搜索一下。

 

项目目录

image

项目的目录主要包括一下几大块:

analysis:该jar包下面主要包括java类的编译、加载以及类、字段的分析

module:此jar包下的内容主要是一些对领域建模的实体类,领域对象中属性的实体类,以及文件类型的实体类等等,如果不太理解,下面会详细讲解

utils:该jar包下面主要包括了一些工具类,如Velocity的引擎工具类,classloader清理的工具类,以及代码生成的入口等…

templates:该目录下的内容主要是一些Velocity的模板,描述要生成的代码的模板

module包

module包下,包含两个子包,一个是core,一个是pojo,其中core下面的内容是关于领域建模的实体类与领域对象中属性的两个实体类。类图如下:

image

EntityModel:

是领域建模的实体类,一个EntityModel代表一个领域类,也就是我们要分析生成代码的一个对象,

成员变量:

name:是领域类的简单的类名称,不包含包名

className:领域类的全类名,包含包名。

方法:

getLastPackageName():用来获取改类的包名的最后一个包的名字

如:com.yunzaipiao.user.UserModel ,则name为:UserModel,className: com.yunzaipiao.user.UserModel。

getLastPackageName()返回:user

FieldModel:

该类为领域对象属性的实体类,一个FieldModel代表领域对象的一个属性,后面会介绍怎么分析领域对象生成FieldModel实体类

成员变量:

name:字段的名称

type:字段的类型,如果为包装类,则是有包名的,如:java.lang.String

方法:

getNameToFirstLetterUpper:返回将字段名的第一个字母转为大写后的字符串,如:name=”age”则返回:Age

getSimpleType:返回不包含报名的类型名称,如:类型为java.lang.String 则该方法返回:String

 

pojo包下面的类主要是跟文件有关的实体类,类图如下:

image

NewFileType:

文件类型枚举类,包装了要创建的文件类型,如:form文件、assembler文件。这里创建的两个文件为我自己定义的,可以随便定义要创建的文件

NewFile:

该类为所有生成文件实体的父类,包含要创建文件的名称、项目路径、包名,以及文件类型

方法:

process:该方法为一个抽象方法,由子类实现,实现后的方法用来根据子类的类型生成新文件

getPath:获取要生成的文件的路径,如下代码,根据项目路径、包名、文件名合并成一个新的文件名称。

protected String getPath() {
  return MessageFormat.format("{0}/src/{1}/{2}.java", projectPath, //
        getPackageName().replaceAll("\\.", "/"), getName());
  }

 

FormNewFile:

该类为NewFile的子类,用来生成Form文件,在项目开发过程中有领域类,如各种Model,也有一些数据传输类(VO),这里要生成的Form文件就是VO。该类中包含一些成员变量,存储要生成Form类的名称、字段信息、模板路径以及要导入的包。

成员变量:

fieldMap:存储要生成的Form类字段信息,key为字段名,value为类型,改字段在构造方法中初始化,如下代码:

image

imports:用来获取要导入的包

entityModel:存储要生成Form类的名称等信息

TEMPLATE_PATH:静态变量,存储要生成代码的模板文件

方法:

process:该方法实现了父类的process方法,处理生成新文件的功能,代码如下,如有不明白的地方可以上网搜索Velocity的使用方法

AssemblerNewFile:

该类的功能就是生成Assembler文件的一个实体类,Assembler类是一个包装器类,主要用来转换po与vo的一个工具类

WrapperType:

用来包装基本数据类型类,暂时好像没有用到,是从原koala项目摘过来的。

 

Analysis包

此包下面的类为编译项目、加载项目以及分析类等工具,其中编译项目的工具类并没有使用,因为编译项目涉及到加载jar包等需要更多的代码且eclipse可以帮助我们编译,所以暂时没有做编译项目的工作。

主要方式为寻找到eclipse编译后的目录,然后加载所有的类,然后通过指定的java文件名获取类,再然后通过反射分析相关的字段等信息。类图如下:

image

CURDClassCompiler:

该类为编译java源文件类,由于此项目暂未使用就不在详细讲解了,有兴趣可以查看源码看看怎么实现的,暂时只能编译没有引用任何自定义类的源文件,因为编译时没有指定jar包

CURDClassLoader:

该工具类主要用来加载项目的所有jar包与类

成员变量:

classloader : URLClassLoader,这是一个通过URL来加载class的工具类,使用该工具类可以直接指定一个class的文件路径来加载类

webRootPath: web项目的跟路径

webType: 项目类型,这里这种说明一下,由于编写仓促,没有太多细节的优化,这里的webType直接写死了为“WEB”,其实可以设置为普通的java项目,项目类型不同,主要是加载class的路径不同,其他都一样

urlList:存储class及jar包的路径

方法:

initLoader:初始化class路径及jar包路径,由于平时开发项目时,class编译后的路径统一为项目跟路径下的/web/WEB-INF/classes路径下,而jar包的路径为项目跟路径下的/web/WEB-INF/lib/路径下,所以这里初始化class及jar包路径后,调用loadWeb方法,加载项目

private void initLoader() throws MalformedURLException{
    if ("WEB".equals(webType)) {
    String dir = webRootPath + "/web/WEB-INF/classes/";
      String libs = webRootPath + "/web/WEB-INF/lib/";
      loadeWeb(dir, libs);
    } else {
    String dir = webRootPath + "/bin";
    loadJar(dir);
    }
}

loadWeb:该方法将class路径以及所有的jar包存储到成员变量urlList中去,然后构造方法会new一个URLClassLoader,将urlList转换成URL数组后作为参数。然后通过URLClassLoader.loadClass(name)方法可以获取指定的class

private void loadeWeb(String dir, String libs) throws MalformedURLException {
    urlList.add(new URL("file:" + dir));
    File lib = new File(libs);
    File[] jarFiles = lib.listFiles();
    for (File jarFile : jarFiles) {
      logger.info("addJar:" + jarFile.getAbsolutePath());
      urlList.add(new URL("file:" + jarFile.getAbsolutePath()));
    }
  }

forName:通过URLClassLoader.loadClass(name),获取加载后的指定class信息

CURDClassLoader:构造方法,整合了以上方法的功能,首先调用initLoader初始化class、及jar包路径,然后调用loadWeb获取class路径及所有的jar包路径,最后生成一个URLClassLoader。发布一个公有方法forName通过URLClassLoader.loadClass获取指定class的信息。

private CURDClassLoader(String webRootPath) throws MalformedURLException{
  this.webRootPath = webRootPath;
  initLoader();
  URL[] urls = new URL[] {};
  classloader = new URLClassLoader(urlList.toArray(urls));
}
CURDCoreAnalysis:

这个类是领域建模核心,分析领域对象的类名、属性信息等,

成员变量:

classloader:这个是刚才创建的CURDClassLoader 类的一个实例,用来加载要分析的类

方法:

analysis:此方法为该工具类的入口方法,传入一个项目路径、要分析的java文件,然后进行分析类的属性与相关的字段,分析类及属性信息是通过java的反射机制完成的,以下为完整代码,如果有不明白的地方请百度下或留言一起学习吧。

/**
   * 传入选中的JAVA文件的完整路径,对此源文件进行领域建模分析
   * @param srcJava java文件完整的路径,从包名开始,如:com.yunzaipiao.user.User
   * @throws Exception 
   */
  public EntityModel analysis(String projectPath, String srcJava) throws Exception{
//    String binPath = srcPath.replace("/src", "/bin");
//    binPath = binPath.substring(0, binPath.lastIndexOf("/"));
    // 编译
//    new CURDClassCompilerAndLoad().compiler(srcPath, binPath);
    
    classloader = CURDClassLoader.getInstance(projectPath);
    //实体本身进行分析
    EntityModel entity = analysisField(srcJava);
    classloader.close();
    return entity;
  }
  
  /**
   * 分析用户选中的ENTITY类
   * @param srcJava
   */
  private EntityModel analysisField(String srcJava){
  
    if(entityMap.containsKey(srcJava))return entityMap.get(srcJava);
    logger.info("==============对"+srcJava+"进行建模工作============");
    EntityModel entity = new EntityModel();
    entityMap.put(srcJava, entity);
    Class classEntity = null;
    try {
      classEntity = classloader.forName(srcJava);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
    entity.setName(classEntity.getSimpleName());
    entity.setClassName(classEntity.getName());
    
    analysisField(classEntity,entity);
    logger.info("entity.name: " + classEntity.getSimpleName());
    logger.info("entity.className: " + classEntity.getName());
    logger.info("entity.packageName: " + classEntity.getPackage().getName());
    logger.info("==============对"+srcJava+"进行建模完成=============");
    return entity;
  }
  
  /**
   * 传入一个类,解析这个类所拥有的属性
   * @param classEntity
   */
  @SuppressWarnings("rawtypes")
private void analysisField(Class classEntity,EntityModel entity){
  List<Class> classesForAnalysis = new ArrayList<Class>();
  classesForAnalysis.add(classEntity);
    Class supperClass = classEntity.getSuperclass();
  while (supperClass != null) {
  classesForAnalysis.add(supperClass);
  supperClass = supperClass.getSuperclass();
}
  for (int i = classesForAnalysis.size() -1; i >=0; i--) {
  Class classForAnalysis = classesForAnalysis.get(i);
  
  Field[] fields = classForAnalysis.getDeclaredFields();
      Method[] methods = classForAnalysis.getDeclaredMethods();
      Map<String,Method> methodMap = new HashMap<String,Method>();
      for(Method method:methods){
        methodMap.put(method.getName().toLowerCase(), method);
      }
      for(Field field:fields){
        boolean isStatic = Modifier.isStatic(field.getModifiers());
        if(isStatic)continue;
        
        logger.info("分析到属性:"+field.getName()+";类型是:【"+field.getType().getName()+"】");
        //分析FIELD
        createModel(field,entity);
      }
  }
    
  }
  private void createModel(Field field,EntityModel entity){
    FieldModel fieldModel = new FieldModel(field.getName(),field.getType().getName());
    entity.getFields().add(fieldModel);
  }

 

utils

该包下面有三个类,ClassLoaderClear、CodeGenerator、VelocityContextUtils三个工具类,其中ClassLoaderClear为清除与释放加载项目时的一些资源,具体细节请查看源码吧;VelocityContextUtils就是生成一个单例模式的VelocityContext的实例,也没有什么好讲解的,下面主要讲解一下CodeGenerator类。

CodeGenerator:

首先请看一下我们平时做项目的目录结构,

image可以看到,我们的项目目录是在src目录下面就直接是包名了,所以,给我一个java源文件的全路径,我直接截取src前面的字符串,即为项目的路径,src后面的字符串并去除.java字符串即为要分析的java全类名,各位也可以根据自己的项目目录结构进行处理一下,当然这样可能也不太严谨,如果有更好的处理方法请留言出来一块学习吧。

先看一下最关键的一个方法的代码:

  public static void generateCode(String javaPath) throws Exception {
    try {
      String projectPath = javaPath.substring(0,
          javaPath.indexOf("/src") + 1);
      projectPath = projectPath
          .substring(0, projectPath.lastIndexOf("/"));
      logger.info("项目路径是:" + projectPath);
      String srcPath = javaPath.substring(javaPath.indexOf("/src") + 4);
      String srcJava = srcPath.substring(1, srcPath.lastIndexOf(".java"));
      logger.info("要分析的实体类是:" + srcJava);
      srcJava = srcJava.replaceAll("/", ".");
      EntityModel entityModel = CURDCoreAnalysis.getInstance().analysis(
          projectPath, srcJava);
      VelocityContext context = VelocityContextUtils.getVelocityContext();
      context.put("entity", entityModel);
      FormNewFile formNewFile = createFormFile(projectPath, entityModel);
      formNewFile.process();
      AssemblerNewFile assemblerNewFile = createAssemblerFile(
          projectPath, entityModel);
      assemblerNewFile.process();
    } catch (Exception e) {
      throw e;
    }
  }

可以看到该方法,首先通过传入的java源文件的全路径,分析项目路径及java类的全名称,然后通过上面讲过的CURDCoreAnalysis分析类的名称及属性信息,然后通过VelocityContext获取一个实例,将分析到的信息放入velocity属性中,然后创建一个FormNewFile,通过process方法创建Form文件,最后创建Assembler文件。

在上面的generateCode方法中调用了createFormFile、createAssemblerFile两个方法用来创建对应的FormNewFile与AssemblerNewFile,首先看一下createFormFile方法:

  private static FormNewFile createFormFile(String projectPath,
      EntityModel entityModel) {
    String className = entityModel.getClassName();
    String formName = entityModel.getName();
    if (formName.endsWith("Model")) {
      formName = formName.substring(0, formName.indexOf("Model"));
    }
    formName += "Form";
    String packageName = className.substring(0, className.lastIndexOf("."))
        + ".form";
    packageName = packageName.replace(".model.", ".web.");
    FormNewFile formNewFile = new FormNewFile(formName, projectPath,
        packageName, NewFileType.Form, entityModel);
    return formNewFile;
  }

 

由代码出可以看到,如果领域对象的结尾包含Model则去掉,然后在后面加一个后缀Form,然后分析Form文件要存储的路径为.form包下面,这里由于项目特殊的原因,我们的项目中的路径领域对象一般在model包下面,而form对象一般在web包下面,所以后面进行了替换,将model替换为web.然后创建一个FormNewFile对象。

createAssemblerFile与createFormFile类似,也就不在具体讲述了。

集成:

好了,对于领域对象的Form、Assembler文件的生成基本已经完成了,可以调用CodeGenerator.generateCode(javaPath)方法,传入一个java文件的全路径名进行测试一下,如果不出意外会生成对应的两个文件,但是这样肯定不是我们的最终目的,且前面特别讲述了插件的创建方式,所以我们要把该项目集成到前面几节创建的插件项目中去。

第一步,首先将本次创建的项目打包成jar包,导出。项目右键->Export->JAR file,

image

按照图中的选好,且指定好存储位置即可,然后一路确认下去,即可导出jar文件。

第二部:将导出的jar文件以及CodeGeneratorCURD项目依赖的jar包全部拷入插件项目,并添加到编译路径,然后在插件项目的配置文件中,在配置一下,如图:

image

将所有的jar包添加进来

第三部,在弹出菜单的的操作方法中获取当前选中文件的全路径,并且调用生成代码的方法。代码如下:

public Object execute(ExecutionEvent event) throws ExecutionException {
    String path = getSelectPath();
    if (null!=path) {
      try {
        CodeGenerator.generateCode(path);
        MessageDialog.openInformation(
            null,
            "生成代码",
            "生成代码成功!!");
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    return null;
  }
  
  private String getSelectPath(){
    IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
    ISelection selection = page.getSelection();
    IStructuredSelection sel = (IStructuredSelection)selection;
    Object element = sel.getFirstElement();
    IResource resources = null;
    if (element instanceof IResource) {
      resources = (IResource) element;
    }
    if (!(element instanceof IAdaptable)) {
      return null;
    }
    IAdaptable adaptable = (IAdaptable) element;
    Object adapter = adaptable.getAdapter(IResource.class);
    resources = (IResource) adapter;
    String path = resources.getLocationURI().getPath();
    if (null!=path) {
      if (path.startsWith("/") && path.substring(2, 3).equals(":")) {
        path = path.substring(1);
      }
    }
    System.out.println(path);
    return path;
  }

 

Example:

在插件项目中右键,run as Eclipse Application,运行成功后,新建一个web项目,并更改项目的编译class文件的输出路径为:web/WEB-INF/classes路径中,如下图:

image

 

所有完成后,新建一个类,点击生成代码,生成后的目录及代码如下图:

image

好了,写的有点仓促,如果有不明白或不正确的地方欢迎指正。。。

附件链接地址:http://files.cnblogs.com/files/haifeng1990/TestPlugin.rar