Java代碼分析器(四): 代碼改寫技術

通常的工具只能分析代碼,不能改變代碼,除了IDE的重構功能。但咱們仍是有辦法實現的。node

不想讓黑科技失傳,趁着Java 7還在普遍使用,趕忙寫下來(可能沒法支持Java 8)。git

這個小框架讓你看文章前就能上手,快速對代碼庫作分析/改寫,性能很高: https://github.com/sorra/exiagithub

下面介紹通過驗證的具體技術,能局部修改代碼,調API就好了(感謝Eclipse)。文檔裏很難查到這些,痛的回憶…… (有句名言說: 畫一條線值1美圓,知道在哪畫線值9999美圓。)app

核心代碼以下:框架

import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.TextEdit;

CompilationUnit cu = parseAST(...); //parse方法參見系列文章
cu.recordModifications(); //開始記錄AST變化事件
doChangesOnAST(...); //直接在樹上改變結點,參見系列文章
Document document = new Document(content);
TextEdit edits = cu.rewrite(document, formatterOptions); //樹上的變化生成了像diff同樣的東西
edits.apply(document); //應用diff
return document.get(); //獲得新的代碼,未改動的部分幾乎都保持原樣

我用的formatterOptions:eclipse

private static final Map<String, String> formatterOptions = DefaultCodeFormatterConstants.getEclipseDefaultSettings();
    static {
        formatterOptions.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_7);
        formatterOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_1_7);
        formatterOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_7);
        formatterOptions.put(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE);
        formatterOptions.put(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "2");
        formatterOptions.put(DefaultCodeFormatterConstants.FORMATTER_LINE_SPLIT, "100");
        formatterOptions.put(DefaultCodeFormatterConstants.FORMATTER_JOIN_LINES_IN_COMMENTS, DefaultCodeFormatterConstants.FALSE);
        // change the option to wrap each enum constant on a new line
        formatterOptions.put(
            DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ENUM_CONSTANTS,
            DefaultCodeFormatterConstants.createAlignmentValue(
            true,
            DefaultCodeFormatterConstants.WRAP_ONE_PER_LINE,
            DefaultCodeFormatterConstants.INDENT_ON_COLUMN));
    }

若是改動幅度很大,被改的代碼可能會縮進混亂。忍一忍吧,這套API本來會把代碼改錯,我定位到bug,提給Eclipse,他們發現問題很深,最後沒什麼辦法,只能犧牲縮進換來代碼正確性。工具

因爲以上緣由,這套便利的API在Java 8再也不保證支持。聽說只能用原始的ListRewrite來改代碼…… 珍惜着用吧。性能

最後再介紹兩個便利方法:code

  1. ASTNode#delete()
    結點能把自身從樹上移除。調這個方法不須要知道parent結點的類型,用起來就知道方便了。
  2. replaceNode
    我仿寫的方法,能任意替換一個結點,不須要知道parent結點的類型。
public static void replaceNode(ASTNode old, ASTNode neo) {
      StructuralPropertyDescriptor p = old.getLocationInParent();
      if (p == null) {
          // node is unparented
          return;
      }
      if (p.isChildProperty()) {
          old.getParent().setStructuralProperty(p, neo);
          return;
      }
      if (p.isChildListProperty()) {
          List l = (List) old.getParent().getStructuralProperty(p);
          l.set(l.indexOf(old), neo);
      }
    }
相關文章
相關標籤/搜索