17

So, here is a piece of code using CodeModel that generates java code:

    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);

This code generates a .java file:

package foo;
public class Bar {

       int foo() {
        return  5;
    }
}

However, I DO NOT want CodeModel to create a new java file for me. I do have a .java file already and would like to add a few lines of code to a method inside it. So, I would like the API to modify the java file directly/ create a modified copy of it. Is there a way to doing this?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
Jay
  • 2,394
  • 11
  • 54
  • 98
  • Having partial classes [of C#] in java will help to some extent... – Fakrudeen Feb 25 '10 at 17:23
  • C# allows class definitions to span multiple files. If the changes are only additive, you could define [generate] them in a different file, instead of modifying the existing file. All this is moot for you, as java doesn't have partial classes. That's why I added it as a comment. – Fakrudeen Feb 26 '10 at 16:05

4 Answers4

14

I know it's been a while since the original post, but one of the more accessible looking Java transformation libraries appears to be Spoon.

From the Spoon Homepage:

Spoon enables you to transform (see below) and analyze (see example) source code. Spoon provides a complete and fine-grained Java metamodel where any program element (classes, methods, fields, statements, expressions...) can be accessed both for reading and modification. Spoon takes as input source code and produces transformed source code ready to be compiled.

Update: Square have also created the JavaPoet source-code generation library, the fluent API looks simple enough to grasp.

Big Rich
  • 5,864
  • 1
  • 40
  • 64
  • 1
    don't even quite remember why I had asked this question :) .. thanks for answering it anyway - i am rewarding your answer! – Jay Jan 19 '13 at 17:05
  • 1
    Can JavaPoet read in existing source code? I can't find any mention of it in skimming JavaPoet README.md. – JohnC Mar 11 '16 at 21:54
  • 1
    @JohnC, sorry, I haven't used JavaPoet in anger, but from what I can gather, Spoon seems to better fit your requirement. No reason not to combine the 2 libraries for modification and pure generation (i.e. the right tool for the right job). – Big Rich Mar 18 '16 at 12:42
7

...I would like the API to modify the java file directly/ create a modified copy of it. Is there a way to doing this?

JavaParser is an API that allows you to read in Java files, modify them, and get the results as a String.

More specifically, JavaParser parses the file and builds an AST (abstract syntax tree). You can then modify the JavaParser AST representing your source code using the API and retrieve the String representation of the AST.

I do have a .java file already and would like to add a few lines of code to a method inside it.

Here's an example of using JavaParser to add a line onto the end of a method body and print the result:

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 - From JavaParser's javadoc, "This class represents the entire compilation unit. Each java file denotes a compilation unit."

In your code, replace Option.get() calls with proper handling.


An example to add method logging to a class name given on the command line:

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() );
      }
    }
  }
}

This will write the changed source file to standard output. If you put the Main file inside the core project's main package, then you can build the core project's JAR file (e.g., mvn package). Renaming the JAR file to javaparser.jar and then run the Main over all the JAR files:

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

Of course, it would be much more efficient to have Java iterate over a directory tree. Once the .jp files are present and look okay, you can rename them en masse using:

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

This will destroy the original formatting, making it fairly unsuitable for checking into a repository. Some Java 14 statements might not translate into a file that can be compiled. YMMV.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Aro
  • 609
  • 9
  • 15
3

You're really going to need a full parse of the code you want to modify to ensure you insert code into the correct location. I'd have thought your best bet would be to make use of an existing parse tool that allows code to be rewritten, rather than to try and do something by hand.

The Eclipse IDE does something like this to support code refactoring. This article might be helpful.

Richard Miskin
  • 1,260
  • 7
  • 12
0

What you want is a program transformation system. This is a tool that parses your source file, and can apply transformations to modify it, an regenerates source code with the modifications.

A source-to-source transformation system accepts rules of the form of:

lhs -> rhs  if cond

where the lhs and rhs are souce patterns for valid fragments of the language and the cond checks that the rule is safe to apply. (Consider " ?x/?x -> 1 if ?x~=0"; you need the condition to verify that the division is valid).

One such tool is our DMS Software Reengineering Toolkit. DMS has full C, C++, C#, Java, COBOL, Python, PHP and ECMAScript front end parsers (as as many lesser known languages) and can apply such rules directly. DMS also provides symbol table construction and control and data flow analysis, as these are often useful in defining a cond for complex rules. If you want, you can also fall back to a "standard" procedural interface to visit tree nodes and modify ASTs.

Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
  • @Ira thanks. I am however looking at using open source stuff, not commercial ones. – Jay Feb 26 '10 at 14:18
  • Well, you can try TXL http://www.txl.ca or Stratego http://www.strategoxt.org, which are also program transformation systems that have source-to-source rewrite rules. What they don't have, that I beleive is fundamentally necessary to transformation of procedural langauges, is the symbol table and flow analysis tools. We've found these to be indispensable to doing reliable, large scale program transformation. You can try implementing these yourself in the OSS tools, but its a lot of work. YMMV. – Ira Baxter Feb 26 '10 at 14:28
  • Wow. 7 year old answer, that addresses OP's question directly including providing open source alternatives when OP made that clear in his comment... and *now* it draws downvotes? I don't suppose the downvoters are willing to explain why they downvoted. – Ira Baxter Dec 31 '16 at 17:31
  • @IraBaxter Can you comment how your program compares to the ones mentioned in the other answers - fx JavaParser and Spoon? – Hervian Jun 21 '20 at 07:20
  • I haven't used JavaParser or Spoon, so my remarks here a speculative. 1) I don't believe either one of them lets you write source-to-source patterns; you have make your changes to a provided AST using procedural actions. That's a lot more painful. 2) It is really hard to reliably apply a transform to code if you don't know the semantic properties of the items being transformed. Consider the source-to-source rewrite rule "x=x+1" -> "x++". What if x is a string? Then the rule isn't right. Our front ends build Java symbol tables so rules can check this with "if cond". I don't think the others do. – Ira Baxter Jun 22 '20 at 18:17