【问题标题】:Editing/Modifying a .java file programmatically? (not the .class file)以编程方式编辑/修改 .java 文件? (不是 .class 文件)
【发布时间】:2011-01-20 23:45:24
【问题描述】:

所以,这里有一段使用CodeModel 生成java 代码的代码:

    JCodeModel cm = new JCodeModel();
    JDefinedClass dc = cm._class("foo.Bar");
    JMethod m = dc.method(0, int.class, "foo"); 
    m.body()._return(JExpr.lit(5));
    File f = new File("C:/target/classes");
    f.mkdirs();
    cm.build(f);

此代码生成一个 .java 文件:

package foo;
public class Bar {

       int foo() {
        return  5;
    }
}

但是,我希望 CodeModel 为我创建一个新的 java 文件。我已经有一个 .java 文件,并且想在其中的方法中添加几行代码。所以,我希望 API 直接修改 java 文件/创建它的修改副本。有没有办法做到这一点?

【问题讨论】:

  • 在 java 中拥有 [of C#] 的部分类将在一定程度上有所帮助...
  • C# 允许类定义跨越多个文件。如果更改只是附加的,您可以在不同的文件中定义 [生成] 它们,而不是修改现有文件。所有这些对你来说都是没有意义的,因为 java 没有部分类。这就是我将其添加为评论的原因。

标签: java code-generation


【解决方案1】:

我知道距离最初的帖子已经有一段时间了,但看起来更易于访问的 Java 转换库之一似乎是 Spoon

来自Spoon Homepage

Spoon 使您能够转换(见下文)和分析(见示例) 源代码。 Spoon 提供了完整且细粒度的 Java 元模型 任何程序元素(类、方法、字段、语句、 表达式...) 可用于阅读和修改。 Spoon 将源代码作为输入并生成转换后的源代码 准备编译。

更新:Square 还创建了JavaPoet 源代码生成库,流畅的 API 看起来很简单。

【讨论】:

  • 不记得我为什么问这个问题 :) .. 谢谢你的回答 - 我奖励你的回答!
  • JavaPoet 可以读取现有源代码吗?我在浏览 JavaPoet README.md 时找不到任何提及。
  • @JohnC,抱歉,我没有在愤怒中使用过 JavaPoet,但据我所知,Spoon 似乎更符合您的要求。没有理由不将这 2 个库结合起来进行修改和纯生成(即正确工作的正确工具)。
【解决方案2】:

...我希望 API 直接修改 java 文件/创建它的修改副本。有没有办法做到这一点?

JavaParser 是一个 API,它允许您读取 Java 文件、修改它们并以字符串的形式获取结果。

更具体地说,JavaParser 解析文件并构建 AST(抽象语法树)。然后,您可以使用 API 修改表示您的源代码的 JavaParser AST 并检索 AST 的字符串表示。

我已经有一个 .java 文件,想在其中的方法中添加几行代码。

这是一个使用 JavaParser 在方法体末尾添加一行并打印结果的示例:

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Optional;

public class Main {
  public static void someMethod() {
    // Lines will be added here.
  }

  public static void main( String[] args ) throws FileNotFoundException {
    String newStatement = "System.out.println(\"Hello world!\");";
    File myClassSourceFile = new File( "Main.java" );

    JavaParser parser = new JavaParser();

    ParseResult<CompilationUnit> pr = parser.parse( myClassSourceFile );
    Optional<CompilationUnit> ocu = pr.getResult();

    if( ocu.isPresent() ) {
      CompilationUnit cu = ocu.get();
      ClassOrInterfaceDeclaration decl = cu.getClassByName( "Main" ).get();
      MethodDeclaration method = decl.getMethods().get( 0 );
      method.getBody().ifPresent( ( b ) -> b.addStatement( newStatement ) );
    }

    // Print out the resulting Java source code.
    System.out.println( pr.toString() );
  }
}

CompilationUnit - 来自 JavaParser 的 javadoc,“这个类代表整个编译单元。每个 java 文件表示一个编译单元。”

在您的代码中,将Option.get() 调用替换为适当的处理方式。


将方法日志记录添加到命令行中给出的类名的示例:

public class Main {
  public static void main( final String[] args ) throws FileNotFoundException {
    final File sourceFile = new File( args[ 0 ] );
    final JavaParser parser = new JavaParser();
    final ParseResult<CompilationUnit> pr = parser.parse( sourceFile );
    final Optional<CompilationUnit> ocu = pr.getResult();

    if( ocu.isPresent() ) {
      final CompilationUnit cu = ocu.get();
      final List<TypeDeclaration<?>> types = cu.getTypes();

      for( final TypeDeclaration<?> type : types ) {
        final List<MethodDeclaration> methods = type.getMethods();

        for( final MethodDeclaration method : methods ) {
          final Optional<BlockStmt> body = method.getBody();
          final String m = format( "%s::%s( %s )",
                                   type.getNameAsString(),
                                   method.getNameAsString(),
                                   method.getParameters().toString() );

          final String mBegan = format(
              "System.out.println(\"BEGAN %s\");", m );
          final String mEnded = format(
              "System.out.println(\"ENDED %s\");", m );

          final Statement sBegan = parseStatement( mBegan );
          final Statement sEnded = parseStatement( mEnded );

          body.ifPresent( ( b ) -> {
            final int i = b.getStatements().size();

            b.addStatement( 0, sBegan );

            // Insert before any "return" statement.
            b.addStatement( i, sEnded );
          } );
        }

        System.out.println( cu.toString() );
      }
    }
  }
}

这会将更改的源文件写入标准输出。如果将Main 文件放在core 项目的主包中,则可以构建core 项目的JAR 文件(例如mvn package)。将 JAR 文件重命名为 javaparser.jar,然后对所有 JAR 文件运行 Main

for i in $(find . -type f -name "*.java"); do \
  java -cp javaparser.jar com.github.javaparser.Main "$i" > \
    "$i.jp";
done

当然,让 Java 遍历目录树会更有效率。一旦.jp 文件出现并且看起来没问题,您可以使用以下命令对它们进行整体重命名:

find . -type f -name "*jp" -size +100c -exec \
  sh -c 'mv {} $(dirname {})/$(basename {} .jp)' \;

这将破坏原始格式,使其非常不适合检入存储库。某些 Java 14 语句可能无法转换为可编译的文件。 YMMV。

【讨论】:

    【解决方案3】:

    您确实需要对要修改的代码进行完整解析,以确保将代码插入正确的位置。我原以为你最好的选择是利用允许重写代码的现有解析工具,而不是尝试手动做某事。

    Eclipse IDE 做了类似的事情来支持代码重构。这个article 可能会有所帮助。

    【讨论】:

      【解决方案4】:

      你想要的是program transformation system。这是一个解析你的源文件的工具,并且可以 应用转换来修改它,然后重新生成修改后的源代码。

      源到源转换系统接受以下形式的规则:

      lhs -> rhs  if cond
      

      lhsrhs 是语言有效片段的源模式,cond 检查规则是否可以安全应用。 (考虑“?x/?x -> 1 if ?x~=0”;您需要条件来验证除法是否有效)。

      我们的DMS Software Reengineering Toolkit 就是一个这样的工具。 DMS 具有完整的 C、C++、C#、Java、COBOL、Python、PHP 和 ECMAScript 前端解析器(与许多鲜为人知的语言一样),并且可以直接应用这些规则。 DMS 还提供符号表构造和控制以及数据流分析,因为这些通常在为复杂规则定义 cond 时很有用。如果需要,您还可以退回到“标准”程序界面来访问树节点并修改 AST。

      【讨论】:

      • @Ira 谢谢。然而,我正在考虑使用开源的东西,而不是商业的。
      • 嗯,你可以试试 TXL txl.ca 或 Stratego strategoxt.org,它们也是具有源到源重写规则的程序转换系统。他们所没有的,我相信对于程序语言的转换从根本上来说是必要的,是符号表和流分析工具。我们发现这些对于进行可靠的大规模项目转型是必不可少的。您可以尝试在 OSS 工具中自己实现这些,但工作量很大。 YMMV。
      • 哇。 7 年前的答案,直接解决了 OP 的问题,包括当 OP 在他的评论中明确表示时提供开源替代方案......并且 现在 它会否决?我不认为投反对票的人愿意解释他们投反对票的原因。
      • @IraBaxter 您能否评论一下您的程序与其他答案中提到的程序相比如何 - fx JavaParser 和 Spoon?
      • 我没有使用过 JavaParser 或 Spoon,所以我在这里的评论是推测性的。 1)我不相信它们中的任何一个都可以让您编写源到源模式;您已使用程序操作对提供的 AST 进行了更改。那更痛苦。 2) 如果您不知道要转换的项目的语义属性,则很难可靠地将转换应用于代码。考虑源到源重写规则“x=x+1”->“x++”。如果 x 是一个字符串呢?那么规则就不对了。我们的前端构建 Java 符号表,因此规则可以使用“if cond”进行检查。我不认为其他人会这样做。
      猜你喜欢
      • 1970-01-01
      • 2014-06-06
      • 1970-01-01
      • 1970-01-01
      • 2013-09-12
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 2016-08-17
      相关资源
      最近更新 更多