Any possible ways to add a java code to existing java file programatically?

146 views Asked by At

I have an interface and class file auto-generated using a custom maven plugin I created. The plugin will read the necessary data from a JSON file and create me a Java files using Jenesis4Java (Mojo code provided below).

REQUIREMENT - i have to traverse to the already generated file and add a new method or code in that file. Is there any way to achieve this from Mojo? Take a look at below generated code, so I have to add a new abstract method to it.

I can only regenerate the same file from beginning but not able to add to existing code.

The following code was generated-

   /**
 * Customer360 interface.
 */

import java.io.Serializable;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

import javax.ws.rs.core.*;

import reactor.core.publisher.Mono;


@Path(value = "/")
public interface Customer360 {
    @GET
    @Path(value = "")
    @Produces(value = "application/json")
    Mono<Response> casecreation(@Context
    HttpHeaders httpHeaders, @Context
    UriInfo uriInfo);

    @GET
    @Path(value = "")
    @Produces(value = "application/json")
    Mono<Response> getCustomerDetails(@Context
    HttpHeaders httpHeaders, @Context
    UriInfo uriInfo);

    @GET
    @Path(value = "")
    @Produces(value = "application/json")
    Mono<Response> prefetch(@Context
    HttpHeaders httpHeaders, @Context
    UriInfo uriInfo);
}

In the mojo file I have written the logic to generate this and another class file. (Mojo refers to java file in maven plugin creation process)

Mojo file for reference-

import net.sf.json.JSONSerializer;
import net.sourceforge.jenesis4java.*;
import net.sourceforge.jenesis4java.impl.MCodeWriter;
import net.sourceforge.jenesis4java.jaloppy.JenesisJalopyEncoder;
import org.apache.commons.io.IOUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.awt.*;
import java.io.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

//import reactor.core.publisher.Mono;


import java.util.Iterator;
import java.util.Map;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.*;

@Mojo(name = "generate-code", defaultPhase = LifecyclePhase.COMPILE)
public class GenerateApiResource extends AbstractMojo {

    @Parameter(defaultValue = "${project}", required = true, readonly = true)
    MavenProject project;

    @Parameter(defaultValue = "src/main/java", required = true)
    protected File outputJavaDirectory;

    @Parameter(defaultValue = "src/main/java", required = true)
    protected File outputJavaDirectory2;

    @Parameter
    protected String[] endpoints;

    private String apiName;


    private AbstractMethod mtr;
    private PackageClass cls ;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {

        this.apiName = endpoints[0];
        System.out.println("API TO USE  1= >> "+apiName);
        this.apiName = endpoints[1];
        System.out.println("API TO USE  2= >> "+apiName);

        this.apiName = "Customer360";
        if (this.project != null) {
            this.project.addCompileSourceRoot(this.outputJavaDirectory.getAbsolutePath());
            this.project.addCompileSourceRoot(this.outputJavaDirectory2.getAbsolutePath());
        }

        /*if (!this.outputJavaDirectory.mkdirs()) {
            getLog().error("Could not create source directory!");
        } else {

        }*/
        try {
            generateJavaCode();
        } catch (IOException e) {
            throw new MojoExecutionException("Could not generate Java source code!", e);
        }

        /*if (!this.outputJavaDirectory2.mkdirs()) {
            getLog().error("Could not create source directory!");
        } else {

        } */

        try {
            generateJavaCode2();
        } catch (IOException e) {
            throw new MojoExecutionException("Could not generate Java source code!", e);
        }


    }

    private void generateJavaCode2() throws IOException {
        System.setProperty("jenesis.encoder", JenesisJalopyEncoder.class.getName());
        // Get the VirtualMachine implementation.
        VirtualMachine vm = VirtualMachine.getVirtualMachine();

        // Instantiate a new CompilationUnit. The argument to the
        // compilation unit is the "codebase" or directory where the
        // compilation unit should be written.

        // Make a new compilation unit rooted to the given sourcepath.
        CompilationUnit unit = vm.newCompilationUnit(this.outputJavaDirectory2.getAbsolutePath());

        // Set the package namespace.
        unit.setNamespace("com.cs.frontline.apiimplementations");

        unit.addImport("javax.inject.Inject");
        unit.addImport("javax.ws.rs.core.Context");
        unit.addImport("javax.ws.rs.core.HttpHeaders");
        unit.addImport("javax.ws.rs.core.Response");
        unit.addImport("javax.ws.rs.core.UriInfo");
        unit.addImport("org.springframework.context.annotation.Scope");
        unit.addImport("org.springframework.stereotype.Component");
        unit.addImport(String.format("com.cs.frontoffice.api.%s",apiName));
        unit.addImport("com.cs.frontoffice.dataorchestrationengine.EndPointHandler");
        unit.addImport("reactor.core.publisher.Mono");

        PackageClass cls = unit.newPublicClass(String.format("%sImpl",apiName));
        cls.addImplements(String.format("%s",apiName));
        unit.setComment(Comment.D, "The API Implementation class.");
        cls.newField(vm.newType("EndPointHandler"),"endPointHandler").addAnnotation("Inject");

        //READ FROM JSON FILE
        JSONParser parser = new JSONParser();
        JSONObject jsonObject;
        JSONArray jsonArray;
        try{
            // parsing file
            File file = new File(String.format("src/main/resources/%s.json",apiName));
            jsonArray = (JSONArray) parser.parse(new FileReader(file));

            for(Object obj: jsonArray){
                JSONObject apiObj = (JSONObject) obj;

                String operationId = (String) apiObj.get("operationId");
                String method = (String) apiObj.get("method");
                String endPointFunction = (String) apiObj.get("endPointFunction");

                ClassMethod mtr = cls.newMethod(vm.newType("Mono<Response>"),operationId);
                mtr.setAccess(Access.PUBLIC);

                if(method == "POST" || method == "PUT"){
                    mtr.addParameter(vm.newType("String"),"requestBodyStr");
                }

                ClassType clsType = vm.newType("@Context HttpHeaders");
                ClassType clsType2 = vm.newType("@Context UriInfo");

                mtr.addParameter(clsType,"httpHeaders");
                mtr.addParameter(clsType2,"uriInfo");

                mtr.addAnnotation("Override");

                Try tr = mtr.newTry();
                tr.newCatch(vm.newType("Exception"),"e");

                Let letx = tr.newLet(vm.newType("Mono<Response>"));

                if(method.equals("GET")){
                    letx.addAssign("responseMap",vm.newInvoke("endPointHandler",String.format("%s",endPointFunction))
                            .addArg(vm.newVar("new Object() {}.getClass().getEnclosingMethod().getName()"))
                            .addArg(vm.newNull())
                            .addVarriableArg("uriInfo")
                            .addVarriableArg("httpHeaders"));

                } else {

                    letx.addAssign("responseMap",vm.newInvoke("endPointHandler","getEndpointResponse")
                            .addArg(vm.newVar("new Object() {}.getClass().getEnclosingMethod().getName()"))
                            .addArg(vm.newVar("new JSONObject(requestBodyStr)"))
                            .addVarriableArg("uriInfo")
                            .addVarriableArg("httpHeaders"));

                }

                tr.newReturn().setExpression(vm.newVar("responseMap"));

                mtr.newReturn().setExpression(vm.newNull());



            }
        } catch (Exception e){
            System.out.println("File FAILED ======");
            System.out.println(e);
        }



        unit.encode();



    }

    private void generateJavaCode() throws IOException {
        System.setProperty("jenesis.encoder", JenesisJalopyEncoder.class.getName());

        // Get the VirtualMachine implementation.
        VirtualMachine vm = VirtualMachine.getVirtualMachine();

        // Instantiate a new CompilationUnit. The argument to the
        // compilation unit is the "codebase" or directory where the
        // compilation unit should be written.
        //
        // Make a new compilation unit rooted to the given sourcepath.
        CompilationUnit unit = vm.newCompilationUnit(this.outputJavaDirectory.getAbsolutePath());

        // Set the package namespace.
        unit.setNamespace("com.cs.frontoffice.api");

        // Add an import statement for fun.
        unit.addImport("java.io.Serializable");
        unit.addImport("javax.ws.rs.GET");
        unit.addImport("javax.ws.rs.Path");
        unit.addImport("javax.ws.rs.Produces");
        unit.addImport("javax.ws.rs.core.*");
        unit.addImport("reactor.core.publisher.Mono");

        // Comment the package with a javadoc (DocumentationComment).
        unit.setComment(Comment.D, "Auto-Generated using the Jenesis Syntax API");


        // Make a new interface.
        Interface itr = unit.newPublicInterface(String.format("%s",apiName));
        itr.addAnnotation("Path").addAnntationAttribute("value").setValue(vm.newString("/"));

        // Comment the class with a javadoc (DocumentationComment).
        unit.setComment(Comment.D, String.format("%s interface.",apiName));
        ClassType t = vm.newType("Mono<Response>");


        //READ FROM JSON FILE
        JSONParser parser = new JSONParser();
        JSONObject jsonObject;
        JSONArray jsonArray;
        try{
            // parsing file
            File file = new File(String.format("src/main/resources/%s.json",apiName));
            jsonArray = (JSONArray) parser.parse(new FileReader(file));

            for(Object obj: jsonArray){
                JSONObject apiObj = (JSONObject) obj;

                String operationId = (String) apiObj.get("operationId");
                String path = (String) apiObj.get("path");
                String method = (String) apiObj.get("method");

                AbstractMethod mtr = itr.newMethod(vm.newType("Mono<Response>"),operationId);
                mtr.addAnnotation(String.format("%s",method));
                mtr.addAnnotation("Path").addAnntationAttribute("value").setValue(vm.newString(String.format("%s",path)));
                mtr.addAnnotation("Produces").addAnntationAttribute("value").setValue(vm.newString("application/json"));

                if(method.equals("POST") || method.equals("PUT")){
                    mtr.addParameter(vm.newType("@RequestBody String"),"requestBodyStr");
                }
                ClassType clsType = vm.newType("@Context HttpHeaders");
                System.out.println(clsType.getName());


                ClassType clsType2 = vm.newType("@Context UriInfo");
                System.out.println(clsType.getName());

                mtr.addParameter(clsType,"httpHeaders");
                mtr.addParameter(clsType2,"uriInfo");

                //Print interface
                System.out.println(mtr);

            }
        } catch (Exception e){
            System.out.println("File FAILED ======");
            System.out.println(e);
        }


        // Write the java file.
        unit.encode();

    }
}
1

There are 1 answers

1
Andrew On

If you can represent your generated code in the Mojo as a String, you can easily edit it with Spoon. Let's say your generated code is in the String generatedCode, then the following will add an abstract method to it:

// create a Spoon model of the generated code
Launcher launcher = new Launcher();
VirtualFile virtualFile = new VirtualFile(generatedCode);
launcher.addInputResource(virtualFile);
launcher.buildModel();
CtInterface ctInterface = launcher.getModel().getRootPackage().getType("Customer360");

// create an abstract method
String codeWithAbstractMethod = "package pkg; abstract class AbstractClass { abstract void abstractMethod();} ";
CtClass<?> classWithAbstractMethod = Launcher.parseClass(codeWithAbstractMethod);
CtMethod abstractMethod = classWithAbstractMethod.getMethodsByName("abstractMethod").get(0);

// add abstract method to the generated code
ctInterface.addMethod(abstractMethod);
String prettyPrint = ctInterface.toString();

prettyPrint will hold a String representation of the transformed generated code. The abstract method here is only an example, Spoon has many useful tools for creating complex code elements which you can find in the documentation.