Package cn.org.rapid_framework.generator

Source Code of cn.org.rapid_framework.generator.Generator$GeneratorModel

package cn.org.rapid_framework.generator;

import static cn.org.rapid_framework.generator.GeneratorConstants.GENERATOR_EXCLUDES;
import static cn.org.rapid_framework.generator.GeneratorConstants.GENERATOR_INCLUDES;
import static cn.org.rapid_framework.generator.GeneratorConstants.GENERATOR_OUTPUT_ENCODING;
import static cn.org.rapid_framework.generator.GeneratorConstants.GENERATOR_REMOVE_EXTENSIONS;
import static cn.org.rapid_framework.generator.GeneratorConstants.GENERATOR_SOURCE_ENCODING;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.org.rapid_framework.generator.util.AntPathMatcher;
import cn.org.rapid_framework.generator.util.BeanHelper;
import cn.org.rapid_framework.generator.util.FileHelper;
import cn.org.rapid_framework.generator.util.FreemarkerHelper;
import cn.org.rapid_framework.generator.util.GLogger;
import cn.org.rapid_framework.generator.util.GeneratorException;
import cn.org.rapid_framework.generator.util.IOHelper;
import cn.org.rapid_framework.generator.util.StringHelper;
import cn.org.rapid_framework.generator.util.ZipUtils;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* 代码生成器核心引擎
*
* 主要提供以下两个方法供外部使用
* <pre>
* generateBy() 用于生成文件
* deleteBy() 用于删除生成的文件
* </pre>
*
* @author badqiu
* @email badqiu(a)gmail.com
*/
public class Generator  {
  private static final String GENERATOR_INSERT_LOCATION = "generator-insert-location";
  private ArrayList<File> templateRootDirs = new ArrayList<File>();
  private String outRootDir;
  private boolean ignoreTemplateGenerateException = true;
  private String removeExtensions = GeneratorProperties.getProperty(GENERATOR_REMOVE_EXTENSIONS);
  private boolean isCopyBinaryFile = true;
 
  private String includes = GeneratorProperties.getProperty(GENERATOR_INCLUDES); // 需要处理的模板,使用逗号分隔符,示例值: java_src/**,java_test/**
  private String excludes = GeneratorProperties.getProperty(GENERATOR_EXCLUDES); // 不需要处理的模板,使用逗号分隔符,示例值: java_src/**,java_test/**
  private String sourceEncoding =  GeneratorProperties.getProperty(GENERATOR_SOURCE_ENCODING);
  private String outputEncoding =  GeneratorProperties.getProperty(GENERATOR_OUTPUT_ENCODING);
 
  public Generator() {
  }
 
  public void setTemplateRootDir(File templateRootDir) {
    setTemplateRootDirs(new File[]{templateRootDir});
  }
  /**
   * 设置模板目录,支持用逗号分隔多个模板目录,如 template/rapid,template/company
   * @param templateRootDir
   */
  public void setTemplateRootDir(String templateRootDir) {
          setTemplateRootDirs(StringHelper.tokenizeToStringArray(templateRootDir,","));
  }
    
  public void setTemplateRootDirs(File... templateRootDirs) {
    this.templateRootDirs = new ArrayList<File>(Arrays.asList(templateRootDirs));
  }

    public void setTemplateRootDirs(String... templateRootDirs) {
        ArrayList<File> tempDirs = new ArrayList<File>();
        for(String dir : templateRootDirs) {
            tempDirs.add(FileHelper.getFile(dir));
        }
        this.templateRootDirs = tempDirs;
    }
    
  public void addTemplateRootDir(File file) {
    templateRootDirs.add(file);
  }

    public void addTemplateRootDir(String file) {
        templateRootDirs.add(FileHelper.getFile(file));
    }
    
  public boolean isIgnoreTemplateGenerateException() {
        return ignoreTemplateGenerateException;
    }
 
  public void setIgnoreTemplateGenerateException(boolean ignoreTemplateGenerateException) {
        this.ignoreTemplateGenerateException = ignoreTemplateGenerateException;
    }

    public boolean isCopyBinaryFile() {
    return isCopyBinaryFile;
  }

  public void setCopyBinaryFile(boolean isCopyBinaryFile) {
    this.isCopyBinaryFile = isCopyBinaryFile;
  }

  public String getSourceEncoding() {
    return sourceEncoding;
  }

  public void setSourceEncoding(String sourceEncoding) {
    if(StringHelper.isBlank(sourceEncoding)) throw new IllegalArgumentException("sourceEncoding must be not empty");
    this.sourceEncoding = sourceEncoding;
  }

  public String getOutputEncoding() {
    return outputEncoding;
  }

  public void setOutputEncoding(String outputEncoding) {
    if(StringHelper.isBlank(outputEncoding)) throw new IllegalArgumentException("outputEncoding must be not empty");
    this.outputEncoding = outputEncoding;
  }
 
  public void setIncludes(String includes) {
    this.includes = includes;
  }
  /** 设置不处理的模板路径,可以使用ant类似的值,使用逗号分隔,示例值:  **\*.ignore */
  public void setExcludes(String excludes) {
    this.excludes = excludes;
  }

  public void setOutRootDir(String rootDir) {
    if(rootDir == null) throw new IllegalArgumentException("outRootDir must be not null");
    this.outRootDir = rootDir;
  }
 
  public String getOutRootDir() {
//    if(outRootDir == null) throw new IllegalStateException("'outRootDir' property must be not null.");
    return outRootDir;
  }

  public void setRemoveExtensions(String removeExtensions) {
    this.removeExtensions = removeExtensions;
  }

    public void deleteOutRootDir() throws IOException {
        if(StringHelper.isBlank(getOutRootDir())) throw new IllegalStateException("'outRootDir' property must be not null.");
        GLogger.println("[delete dir]    "+getOutRootDir());
        FileHelper.deleteDirectory(new File(getOutRootDir()));
    }
    
    /**
     * 生成文件
     * @param templateModel 生成器模板可以引用的变量
     * @param filePathModel 文件路径可以引用的变量
     * @throws Exception
     */
  public Generator generateBy(Map templateModel,Map filePathModel) throws Exception {
    processTemplateRootDirs(templateModel, filePathModel,false);
    return this;
  }

  /**
   * 删除生成的文件
   * @param templateModel 生成器模板可以引用的变量
   * @param filePathModel 文件路径可以引用的变量
   * @return
   * @throws Exception
   */
    public Generator deleteBy(Map templateModel,Map filePathModel) throws Exception {
      processTemplateRootDirs(templateModel, filePathModel,true);
      return this;
   
 
    @SuppressWarnings("unchecked")
  private void processTemplateRootDirs(Map templateModel,Map filePathModel,boolean isDelete) throws Exception {
      if(StringHelper.isBlank(getOutRootDir())) throw new IllegalStateException("'outRootDir' property must be not empty.");
    if(templateRootDirs == null || templateRootDirs.size() == 0) throw new IllegalStateException("'templateRootDirs'  must be not empty");

    GLogger.debug("******* Template reference variables *********",templateModel);
    GLogger.debug("\n\n******* FilePath reference variables *********",filePathModel);
   
    //生成 路径值,如 pkg=com.company.project 将生成 pkg_dir=com/company/project的值
    templateModel.putAll(GeneratorHelper.getDirValuesMap(templateModel));
    filePathModel.putAll(GeneratorHelper.getDirValuesMap(filePathModel));
   
    GeneratorException ge = new GeneratorException("generator occer error, Generator BeanInfo:"+BeanHelper.describe(this));
    List<File> processedTemplateRootDirs = processTemplateRootDirs();
   
    for(int i = 0; i < processedTemplateRootDirs.size(); i++) {
      File templateRootDir = (File)processedTemplateRootDirs.get(i);
      List<Exception> exceptions = scanTemplatesAndProcess(templateRootDir,processedTemplateRootDirs,templateModel,filePathModel,isDelete);
      ge.addAll(exceptions);
    }
    if(!ge.exceptions.isEmpty()) throw ge;
  }

    /**
     * 用于子类覆盖,预处理模板目录,如执行文件解压动作
     **/
  protected List<File> processTemplateRootDirs() throws Exception {
    return unzipIfTemplateRootDirIsZipFile();
  }
 
  /**
   * 解压模板目录,如果模板目录是一个zip,jar文件 . 并且支持指定 zip文件的子目录作为模板目录,通过 !号分隔
   * 指定zip文件: c:\\some.zip  
   * 指定zip文件子目录: c:\some.zip!/folder/
   * @throws MalformedURLException
   **/
  private List<File> unzipIfTemplateRootDirIsZipFile() throws MalformedURLException {
    List<File> unzipIfTemplateRootDirIsZipFile = new ArrayList<File>();
    for(int i = 0; i < this.templateRootDirs.size(); i++) {
      File file = templateRootDirs.get(i);
      String templateRootDir = FileHelper.toFilePathIfIsURL(file);
     
      String subFolder = "";
      int zipFileSeperatorIndexOf = templateRootDir.indexOf("!");
      if(zipFileSeperatorIndexOf >= 0) {
        subFolder = templateRootDir.substring(zipFileSeperatorIndexOf+1);
        templateRootDir = templateRootDir.substring(0,zipFileSeperatorIndexOf);
      }
     
      if(new File(templateRootDir).isFile()) {
        File tempDir = ZipUtils.unzip2TempDir(new File(templateRootDir),"tmp_generator_template_folder_for_zipfile");
        unzipIfTemplateRootDirIsZipFile.add(new File(tempDir,subFolder));
      }else {
          unzipIfTemplateRootDirIsZipFile.add(new File(templateRootDir,subFolder));
      }
    }
    return unzipIfTemplateRootDirIsZipFile;
  }

  /**
     * 搜索templateRootDir目录下的所有文件并生成东西
     * @param templateRootDir 用于搜索的模板目录
     * @param templateRootDirs freemarker用于装载模板的目录
     */
  private List<Exception> scanTemplatesAndProcess(File templateRootDir,List<File> templateRootDirs,Map templateModel,Map filePathModel,boolean isDelete) throws Exception {
    if(templateRootDir == null) throw new IllegalStateException("'templateRootDir' must be not null");
    GLogger.println("-------------------load template from templateRootDir = '"+templateRootDir.getAbsolutePath()+"' outRootDir:"+new File(outRootDir).getAbsolutePath());
   
     List srcFiles = FileHelper.searchAllNotIgnoreFile(templateRootDir);
   
    List<Exception> exceptions = new ArrayList();
    for(int i = 0; i < srcFiles.size(); i++) {
      File srcFile = (File)srcFiles.get(i);
      try {
          if(isDelete){
              new TemplateProcessor(templateRootDirs).executeDelete(templateRootDir, templateModel,filePathModel, srcFile);
          }else {
            long start = System.currentTimeMillis();
              new TemplateProcessor(templateRootDirs).executeGenerate(templateRootDir, templateModel,filePathModel, srcFile);
              GLogger.perf("genereate by tempate cost time:"+(System.currentTimeMillis() - start)+"ms");
          }
      }catch(Exception e) {
        if (ignoreTemplateGenerateException) {
              GLogger.warn("iggnore generate error,template is:" + srcFile+" cause:"+e);
              exceptions.add(e);
          } else {
          throw e;
          }
      }
    }
    return exceptions;
  }
 
  /**
   * 单个模板文件的处理器
   **/
  private class TemplateProcessor {
    private GeneratorControl gg = new GeneratorControl();
    private List<File> templateRootDirs = new ArrayList<File>();
   
    public TemplateProcessor(List<File> templateRootDirs) {
      super();
      this.templateRootDirs = templateRootDirs;
    }

    private void executeGenerate(File templateRootDir,Map templateModel, Map filePathModel ,File srcFile) throws SQLException, IOException,TemplateException {
      String templateFile = FileHelper.getRelativePath(templateRootDir, srcFile);
      if(GeneratorHelper.isIgnoreTemplateProcess(srcFile, templateFile,includes,excludes)) {
        return;
      }
     
      if(isCopyBinaryFile && FileHelper.isBinaryFile(srcFile)) {
        String outputFilepath = proceeForOutputFilepath(filePathModel, templateFile);
        File outputFile = new File(getOutRootDir(),outputFilepath);
        GLogger.println("[copy binary file by extention] from:"+srcFile+" => "+outputFile);
        FileHelper.parentMkdir(outputFile);
        IOHelper.copyAndClose(new FileInputStream(srcFile), new FileOutputStream(outputFile));
        return;
      }
     
            try {
                String outputFilepath = proceeForOutputFilepath(filePathModel,templateFile);
               
                initGeneratorControlProperties(srcFile,outputFilepath);
                processTemplateForGeneratorControl(templateModel, templateFile);
               
                if(gg.isIgnoreOutput()) {
                    GLogger.println("[not generate] by gg.isIgnoreOutput()=true on template:"+templateFile);
                    return;
                }
               
                if(StringHelper.isNotBlank(gg.getOutputFile())) {
                    generateNewFileOrInsertIntoFile(templateFile,gg.getOutputFile(), templateModel);
                }
      }catch(Exception e) {
          throw new RuntimeException("generate oucur error,templateFile is:" + templateFile+" => "+ gg.getOutputFile()+" cause:"+e, e);
      }
    }

      private void executeDelete(File templateRootDir,Map templateModel, Map filePathModel ,File srcFile) throws SQLException, IOException,TemplateException {
          String templateFile = FileHelper.getRelativePath(templateRootDir, srcFile);
            if(GeneratorHelper.isIgnoreTemplateProcess(srcFile, templateFile,includes,excludes)) {
                return;
            }
            String outputFilepath = proceeForOutputFilepath(filePathModel, templateFile);
          initGeneratorControlProperties(srcFile,outputFilepath);
          gg.deleteGeneratedFile = true;
          processTemplateForGeneratorControl(templateModel, templateFile);
          GLogger.println("[delete file] file:"+new File(gg.getOutputFile()).getAbsolutePath());
          new File(gg.getOutputFile()).delete();
      }
     
    private void initGeneratorControlProperties(File srcFile,String outputFile) throws SQLException {
      gg.setSourceFile(srcFile.getAbsolutePath());
      gg.setSourceFileName(srcFile.getName());
      gg.setSourceDir(srcFile.getParent());
      gg.setOutRoot(getOutRootDir());
      gg.setOutputEncoding(outputEncoding);
      gg.setSourceEncoding(sourceEncoding);
      gg.setMergeLocation(GENERATOR_INSERT_LOCATION);
      gg.setOutputFile(outputFile);
    }
 
    private void processTemplateForGeneratorControl(Map templateModel,String templateFile) throws IOException, TemplateException {
      templateModel.put("gg", gg);
      Template template = getFreeMarkerTemplate(templateFile);
      template.process(templateModel, IOHelper.NULL_WRITER);
    }
   
    /** 处理文件路径的变量变成输出路径 */
    private String proceeForOutputFilepath(Map filePathModel,String templateFile) throws IOException {
      String outputFilePath = templateFile;
     
      //TODO 删除兼容性的@testExpression
      int testExpressionIndex = -1;
      if((testExpressionIndex = templateFile.indexOf('@')) != -1) {
        outputFilePath = templateFile.substring(0, testExpressionIndex);
        String testExpressionKey = templateFile.substring(testExpressionIndex+1);
        Object expressionValue = filePathModel.get(testExpressionKey);
        if(expressionValue == null) {
          System.err.println("[not-generate] WARN: test expression is null by key:["+testExpressionKey+"] on template:["+templateFile+"]");
            return null;
        }
        if(!"true".equals(String.valueOf(expressionValue))) {
          GLogger.println("[not-generate]\t test expression '@"+testExpressionKey+"' is false,template:"+templateFile);
            return null;
        }
      }
     
      for(String removeExtension : removeExtensions.split(",")) {
        if(outputFilePath.endsWith(removeExtension)) {
          outputFilePath = outputFilePath.substring(0,outputFilePath.length() - removeExtension.length());
          break;
        }
      }
      Configuration conf = GeneratorHelper.newFreeMarkerConfiguration(templateRootDirs, sourceEncoding,"/filepath/processor/");
     
      //使freemarker支持过滤,如 ${className?lower_case} 现在为 ${className^lower_case}
      outputFilePath = outputFilePath.replace('^', '?');
      return FreemarkerHelper.processTemplateString(outputFilePath,filePathModel,conf);
    }
 
    private Template getFreeMarkerTemplate(String templateName) throws IOException {
      return GeneratorHelper.newFreeMarkerConfiguration(templateRootDirs, sourceEncoding,templateName).getTemplate(templateName);
    }
 
    private void generateNewFileOrInsertIntoFile( String templateFile,String outputFilepath, Map templateModel) throws Exception {
      Template template = getFreeMarkerTemplate(templateFile);
      template.setOutputEncoding(gg.getOutputEncoding());
     
      File absoluteOutputFilePath = FileHelper.parentMkdir(outputFilepath);
      if(absoluteOutputFilePath.exists()) {
        StringWriter newFileContentCollector = new StringWriter();
        if(GeneratorHelper.isFoundInsertLocation(gg,template, templateModel, absoluteOutputFilePath, newFileContentCollector)) {
          GLogger.println("[insert]\t generate content into:"+outputFilepath);
          IOHelper.saveFile(absoluteOutputFilePath, newFileContentCollector.toString(),gg.getOutputEncoding());
          return;
        }
      }
     
      if(absoluteOutputFilePath.exists() && !gg.isOverride()) {
        GLogger.println("[not generate]\t by gg.isOverride()=false and outputFile exist:"+outputFilepath);
        return;
      }
     
      if(absoluteOutputFilePath.exists()) {
          GLogger.println("[override]\t template:"+templateFile+" ==> "+outputFilepath);
      }else {
          GLogger.println("[generate]\t template:"+templateFile+" ==> "+outputFilepath);
      }
      FreemarkerHelper.processTemplate(template, templateModel, absoluteOutputFilePath,gg.getOutputEncoding());
    }
  }

  static class GeneratorHelper {
   
    /**
     * 生成 路径值,如 pkg=com.company.project 将生成 pkg_dir=com/company/project的值
     * @param map
     * @return
     */
    public static Map getDirValuesMap(Map map) {
      Map dirValues = new HashMap();
      Set<Object> keys = map.keySet();
      for(Object key : keys) {
        Object value = map.get(key);
        if(key instanceof String && value instanceof String) {
          String dirKey = key+"_dir";
          String dirValue = value.toString().replace('.', '/');
          dirValues.put(dirKey, dirValue);
        }
      }
      return dirValues;
    }
   
    public static boolean isIgnoreTemplateProcess(File srcFile,String templateFile,String includes,String excludes) {
      if(srcFile.isDirectory() || srcFile.isHidden())
        return true;
      if(templateFile.trim().equals(""))
        return true;
      if(srcFile.getName().toLowerCase().endsWith(".include")){
        GLogger.println("[skip]\t\t endsWith '.include' template:"+templateFile);
        return true;
      }
      templateFile = templateFile.replace('\\', '/');
      for(String exclude : StringHelper.tokenizeToStringArray(excludes,",")) {
        if(new AntPathMatcher().match(exclude.replace('\\', '/'), templateFile)) return true;
      }
      if(StringHelper.isBlank(includes)) {
        return false;
      }
      for(String include : StringHelper.tokenizeToStringArray(includes,",")) {
        if(new AntPathMatcher().match(include.replace('\\', '/'), templateFile)) return false;
      }
      return true;
    }   
   
    private static boolean isFoundInsertLocation(GeneratorControl gg,Template template, Map model, File outputFile, StringWriter newFileContent) throws IOException, TemplateException {
      LineNumberReader reader = new LineNumberReader(new FileReader(outputFile));
      String line = null;
      boolean isFoundInsertLocation = false;
     
      //FIXME 持续性的重复生成会导致out of memory
      PrintWriter writer = new PrintWriter(newFileContent);
      while((line = reader.readLine()) != null) {
        writer.println(line);
        // only insert once
        if(!isFoundInsertLocation && line.indexOf(gg.getMergeLocation()) >= 0) {
          template.process(model,writer);
          writer.println();
          isFoundInsertLocation = true;
        }
      }
     
      writer.close();
      reader.close();
      return isFoundInsertLocation;
   
    public static Configuration newFreeMarkerConfiguration(List<File> templateRootDirs,String defaultEncoding,String templateName) throws IOException {
        Configuration conf = new Configuration();
     
      FileTemplateLoader[] templateLoaders = new FileTemplateLoader[templateRootDirs.size()];
      for(int i = 0; i < templateRootDirs.size(); i++) {
        templateLoaders[i] = new FileTemplateLoader((File)templateRootDirs.get(i));
      }
      MultiTemplateLoader multiTemplateLoader = new MultiTemplateLoader(templateLoaders);
     
      conf.setTemplateLoader(multiTemplateLoader);
      conf.setNumberFormat("###############");
      conf.setBooleanFormat("true,false");
      conf.setDefaultEncoding(defaultEncoding);
     
//      String autoIncludes = new File(new File(templateName).getParent(),"macro.include").getPath();
//      List<String> availableAutoInclude = FreemarkerHelper.getAvailableAutoInclude(conf, Arrays.asList("macro.include",autoIncludes));
//      conf.setAutoIncludes(availableAutoInclude);
//      GLogger.info("[set Freemarker.autoIncludes]"+availableAutoInclude+" for templateName:"+templateName);
     
      List<String> autoIncludes = getParentPaths(templateName,"macro.include");
      List<String> availableAutoInclude = FreemarkerHelper.getAvailableAutoInclude(conf,autoIncludes);
      conf.setAutoIncludes(availableAutoInclude);
      GLogger.trace("set Freemarker.autoIncludes:"+availableAutoInclude+" for templateName:"+templateName+" autoIncludes:"+autoIncludes);
      return conf;
    }

    public static List<String> getParentPaths(String templateName,String suffix) {
      String array[] = StringHelper.tokenizeToStringArray(templateName, "\\/");
      List<String> list = new ArrayList<String>();
      list.add(suffix);
      list.add(File.separator+suffix);
      String path = "";
      for(int i = 0; i < array.length; i++){
        path = path + File.separator + array[i];
        list.add(path + File.separator+suffix);
      }
      return list;
    }
  }
  @SuppressWarnings("unchecked")
  public static class GeneratorModel implements java.io.Serializable{
    private static final long serialVersionUID = -6430787906037836995L;
    /** 用于存放'模板'可以引用的变量 */
    public Map templateModel = new HashMap();
    /** 用于存放'文件路径'可以引用的变量 */
    public Map filePathModel = new HashMap();
   
    public GeneratorModel() {
    }
    public GeneratorModel(Map templateModel, Map filePathModel) {
      this.templateModel = templateModel;
      this.filePathModel = filePathModel;
    }
   
  }
}
TOP

Related Classes of cn.org.rapid_framework.generator.Generator$GeneratorModel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.