1
votes

I have the following error while creating POJO:

java.lang.RuntimeException: act_ge_bytearray: frozen class (cannot edit)
    at javassist.ClassPool.checkNotFrozen(ClassPool.java:587) ~[javassist-3.25.0-GA.jar:na]
    at javassist.ClassPool.makeClass(ClassPool.java:829) ~[javassist-3.25.0-GA.jar:na]
    at javassist.ClassPool.makeClass(ClassPool.java:806) ~[javassist-3.25.0-GA.jar:na]

I create the POJO in a manner below:

Class<?> clazz = null;
try {
    clazz = PojoGenerator.generate( "net.javaforge.blog.javassist.Pojo$Generated", props);
} catch (NotFoundException e) {
    e.printStackTrace();
} catch (CannotCompileException e) {
    e.printStackTrace();
}

The object is being created after selecting particular table. In a first attempt it works but any other fails.

For example in a first attempt after selecting object foo the POJO is created well but after selecting another item the problem appears.

EDIT

I have created the POJO generator class based on the following tutorial: https://blog.javaforge.net/post/31913732423/howto-create-java-pojo-at-runtime-with-javassist

EDIT 2 I am using Vaadin to represent schema of PostgreSQL tables in a Web application.

The following code gets each column of selected table and create properties, getters and setters for given names. Then I am creating as many objects as many is records in the selected table

package pl.registers.spring.view;

import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import javassist.CannotCompileException;
import javassist.NotFoundException;
import pl.registers.spring.service.DbService;
import pl.registers.spring.test.DatabaseTableModel;
import pojo.PojoGenerator;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Route("")
public class TablesView extends VerticalLayout {
    private List<String> tables;

        public TablesView()  {
        Connection con=null;
        try {
            con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/process-engine", "postgres",
                    "postgres");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        ComboBox tablesCombo = new ComboBox();
        try {
            tables = DbService.getInstance().getTableNames(con);
        } catch (SQLException e) {
            e.printStackTrace();
        }

        Map<String, Class<?>> props = new HashMap<String, Class<?>>();
        List items = new ArrayList();
        Grid grid = new Grid();

            try {
                tablesCombo.setItems(DbService.getInstance().getTableNames(con));
            } catch (SQLException e) {
                e.printStackTrace();
            }

            tablesCombo.addValueChangeListener(table -> {
                items.clear();
            try {
                DatabaseTableModel dP = new DatabaseTableModel(DriverManager.getConnection("jdbc:postgresql://localhost:5432/process-engine", "postgres",
                        "postgres"), table.getValue());

                //get column names
                for(int i = 0; i < dP.getColumnCount(); i++) {
                    props.put(dP.getColumnName(i), String.class);
                }

                //get records
                System.out.println("data vector: " + dP.getDataVector());

                //generate Class schema

                Class<?> clazz = null;

                try {
                    clazz = PojoGenerator.generate(
                            "net.javaforge.blog.javassist.Pojo$Generated", props);
//                            table.getValue().toString(), props);
                } catch (NotFoundException e) {
                    e.printStackTrace();
                } catch (CannotCompileException e) {
                    e.printStackTrace();
                }

                //create as many objects as many records
                try {
                    for(int i = 0; i < dP.getDataVector().size(); i++) {
                        items.add(clazz.newInstance());
                    }
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

                List dataVectorCastedToList = dP.getDataVector();

                //set objects values
                for(int i = 0; i <= items.size()-1; i++) {
                    try {
                        clazz.getMethod("Constructor", java.util.List.class).invoke(items.get(i), dataVectorCastedToList.get(i));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                }

                List<String> getters = new ArrayList<>();
                //get methods
                for(int i = 0; i < clazz.getMethods().length-1; i++){
                    String methodName = clazz.getMethods()[i].getName();
                    if(methodName.contains("get")) {
                        getters.add(methodName);
                    }
                }

                //remove getClass method
                if(getters.contains("getClass")) 
     getters.remove("getClass");
                System.out.println("getters" + getters);

                grid.setItems(items);

                //set columns
                getters.forEach(item -> {
                    grid.addColumn(column -> {
                         try {
                            return 
 column.getClass().getMethod(item).invoke(column);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }).setHeader(item);
                });

            } catch (SQLException e) {
                e.printStackTrace();
            }
        });

        //add to layout
        add(tablesCombo, grid);
    }
}

I have also created my own generateInitMethod method in PojoGenerator class which set data for created objects. This is how PojoGenerator looks like:

package pojo;

import javassist.*;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class PojoGenerator {

    public static Class generate(String className, Map<String, Class<?>> properties) throws NotFoundException, CannotCompileException {
        CtClass cc;

        System.out.println("properties passed to pojo generator: " + properties);

        ClassPool pool = ClassPool.getDefault();
        cc = pool.makeClass(className);
        System.out.println("is frozen " + cc.isFrozen());

        cc.debugWriteFile();

        // add this to define a super class to extend
        // cc.setSuperclass(resolveCtClass(MySuperClass.class));

        // add this to define an interface to implement
        cc.addInterface(resolveCtClass(Serializable.class));

        for (Map.Entry<String, Class<?>> entry : properties.entrySet()) {

            cc.addField(new CtField(resolveCtClass(entry.getValue()), entry.getKey(), cc));

            // add getter
            cc.addMethod(generateGetter(cc, entry.getKey(), entry.getValue()));

            // add setter
            cc.addMethod(generateSetter(cc, entry.getKey(), entry.getValue()));
        }

        cc.addMethod(generateInitMethod(cc, properties));
        return cc.toClass();
    }

    private static CtMethod generateGetter(CtClass declaringClass, String fieldName, Class fieldClass)
            throws CannotCompileException {

        String getterName = "get" + fieldName.substring(0, 1).toUpperCase()
                + fieldName.substring(1);

        StringBuffer sb = new StringBuffer();
        sb.append("public ").append(fieldClass.getName()).append(" ")
                .append(getterName).append("(){").append("return this.")
                .append(fieldName).append(";").append("}");
        return CtMethod.make(sb.toString(), declaringClass);
    }

    private static CtMethod generateSetter(CtClass declaringClass, String fieldName, Class fieldClass)
            throws CannotCompileException {

        String setterName = "set" + fieldName.substring(0, 1).toUpperCase()
                + fieldName.substring(1);

        StringBuffer sb = new StringBuffer();
        sb.append("public void ").append(setterName).append("(")
                .append(fieldClass.getName()).append(" ").append(fieldName)
                .append(")").append("{").append("this.").append(fieldName)
                .append("=").append(fieldName).append(";").append("}");
        return CtMethod.make(sb.toString(), declaringClass);
    }

    private static CtMethod generateInitMethod(CtClass declaringClass, Map<String, Class<?>> properties)
            throws CannotCompileException {

        String setterName = "Constructor";

        List<String> fields = new ArrayList<>();

        //set attributes names
        properties.forEach((e, v) -> {
            fields.add(e);
        });

        String body = "";

        for (int i = 0; i < fields.size(); i++) {
            body += "this.".concat(fields.get(i)).concat("=").concat("(String)arguments.get(").concat(String.valueOf(i)).concat(")").concat(";");
        }

        StringBuffer sb = new StringBuffer();

        sb.append("public void ").append(setterName).append("(").append("java.util.List arguments").append(")")
                .append("{")
                .append(body)
                .append("}");

        System.out.println(sb);

        return CtMethod.make(sb.toString(), declaringClass);
    }

    private static CtClass resolveCtClass(Class clazz) throws NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        return pool.get(clazz.getName());
    }

}

Sorry for ugly code but its only a minimal version.

Could anyone provide any guidance on how to approach this problem?

1
Instead of using comments please add additional information to your question via an edit. - Thomas
What is the props you're passing? I assume it is a map as per the tutorial but what does it contain? Ideally you should provide a minimal reproducible example. - Thomas
The props is List of String which are going to be methods names. - Jake Cooper
In that case your code is deviating from the tutorial so we need a minimal reproducible example even more. Without know what you are actually doing it's very hard to help and all we might be able to do is guess. - Thomas
I have updated the question with minimal code. - Jake Cooper

1 Answers

0
votes

Actually it's a check done by Javassist library as you cannot have twice the same class name and package in Java:

Class<?> clazz = PojoGenerator.generate( "net.javaforge.blog.javassist.Pojo$Generated", props);
Class<?> clazz2 = PojoGenerator.generate( "net.javaforge.blog.javassist.Pojo$Generated", props);

The above is gonna fail as the Pojo class has been generated already by the first statement. The following one is going to work as you're naming the second class Pojo2:

Class<?> clazz = PojoGenerator.generate( "net.javaforge.blog.javassist.Pojo$Generated", props);
Class<?> clazz2 = PojoGenerator.generate( "net.javaforge.blog.javassist.Pojo2$Generated", props);

I see from your full example that this is generated dinamically by some external call, so, in order to solve it you may be find a way to store the result of your table generation (ie a local map) and call the generate only if this is missing. It would be something like:

Map<String, Class<?>> cache = new HashMap<String, Class<?>>();    
...
Class<?> clazz = null;
try {
    if (cache.containsKey("your.package.yourTableName$Generated")) {
        clazz = cache.get("your.package.yourTableName$Generated");
    } else {
        clazz = PojoGenerator.generate( "your.package.yourTableName$Generated", props);
        cache.put("your.package.yourTableName$Generated", clazz);
    }
}...