Use JCodeModel to generate toString() method

730 views Asked by At

I am attempting to generate Java Value Objects using com.sun.codemodel.JCodeModel.

I have managed to generate hashcode() and equals() methods but I am struggling with toString();

I require the following toString() implementation

return "ClassName [field1 = " + field1 + ", field2 = " + field2 ... ", fieldN = " + fieldN + "]";

How do I create a JCodeModel JExpression that contains JExpr.lit(field1.name()) concatenated with JExpr.ref(fieldVar.name())?

All i've managed to do is generate a string literal resembling:-

return "ClassName [field1 =  field1 + field2 = field2 ... fieldN = + fieldN + ]";

Heres my skeleton method so far:-

final Map<String, JFieldVar> fields = jclass.fields();
final JMethod toString = jclass.method(JMod.PUBLIC, String.class, "toString");
final Set<String> excludes = new HashSet<String>(Arrays.asList(ruleFactory.getGenerationConfig().getToStringExcludes()));

final JBlock body = toString.body();

for (JFieldVar fieldVar : fields.values()) {
    if (excludes.contains(fieldVar.name()) || ((fieldVar.mods().getValue() & JMod.STATIC) == JMod.STATIC)) {
        continue;
    }

    ??????????????

}

body._return(?????????);

toString.annotate(Override.class);
1

There are 1 answers

2
Marco13 On BEST ANSWER

The key point here is likely that you can combine multiple JExpression objects with the + operator by using the JExpression#plus method.

Here is an example that contains the definition of a simple example class, and a method to generically generate the toString method:

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.writer.SingleStreamCodeWriter;

public class CodeModelToStringTest
{
    public static void main(String[] args) throws Exception
    {
        JCodeModel codeModel = new JCodeModel();
        JDefinedClass definedClass = codeModel._class("com.example.Example");

        definedClass.field(JMod.PUBLIC, String.class, "exampleString");
        definedClass.field(JMod.PROTECTED, int.class, "exampleInt");
        definedClass.field(JMod.PRIVATE, float.class, "exampleFloat");

        definedClass.field(JMod.PRIVATE, String.class, "excludedString");
        definedClass.field(JMod.STATIC, String.class, "staticString");

        createToStringMethod(definedClass, Arrays.asList("excludedString"));

        CodeWriter codeWriter = new SingleStreamCodeWriter(System.out);
        codeModel.build(codeWriter);
    }

    private static void createToStringMethod(
        JDefinedClass definedClass,
        Collection<String> excludedFieldNames)
    {
        Map<String, JFieldVar> fields = definedClass.fields();
        JMethod toString =
            definedClass.method(JMod.PUBLIC, String.class, "toString");
        toString.annotate(Override.class);

        JBlock body = toString.body();

        JExpression expression = JExpr.lit(definedClass.name() + " [");

        boolean first = true;
        for (JFieldVar fieldVar : fields.values())
        {
            if ((fieldVar.mods().getValue() & JMod.STATIC) == JMod.STATIC)
            {
                continue;
            }
            if (excludedFieldNames.contains(fieldVar.name()))
            {
                continue;
            }
            if (!first)
            {
                expression = expression.plus(JExpr.lit(", "));
            }
            expression = expression.plus(JExpr.lit(fieldVar.name()+" = "));
            expression = expression.plus(JExpr.ref(fieldVar.name()));
            first = false;
        }
        expression = expression.plus(JExpr.lit("]"));

        body._return(expression);


    }
}

The generated class with the toString method is shown here:

package com.example;


public class Example {

    public String exampleString;
    protected int exampleInt;
    private float exampleFloat;
    private String excludedString;
    static String staticString;

    @Override
    public String toString() {
        return ((((((((("Example ["+"exampleString = ")+ exampleString)+", ")+"exampleInt = ")+ exampleInt)+", ")+"exampleFloat = ")+ exampleFloat)+"]");
    }

}

The fact that CodeModel inserts ( brackets ) around each binary operation causes the code to not look so pretty. But it's understandable: Otherwise, they would have to take operator precedences into account, and the usage and the code generation itself would likely be far harder.

However, the result of this toString method will be

Example [exampleString = null, exampleInt = 0, exampleFloat = 0.0]

which should be what you expected, based on your examples.