5

I'm looking for an API which generates me source code that would generate objects equal to the ones I passed to the library.

public static void main(String[] args) {
  int[] arr = new int[3];
  arr[0] = 3;
  arr[1] = 4;
  arr[2] = 5;

  // looking for this one
  MagicCode.generateCode(arr);
}

Should generate

  int[] arr = new int[3];
  arr[0] = 3;
  arr[1] = 4;
  arr[2] = 5;

or

  int[] arr = new int[] { 3, 4, 5 };

So I want to pass an Object and get the Java Source Code that would create an object equal to my initial Object.

That should not only work for primitive types but for my own objects too:

public static void main(String[] args) {
  Condition condition = new Condition();
  condition.setNumber(2);

  // looking for this one
  MagicCode.generateCode(condition);
}

Should generate

  Condition c1 = new Condition();
  c1.setNumber(2);

as a String which can then be pasted to a Java Source file.

EDIT

I don't want to bypass the compiler.

I'm about to rewrite a component that isn't tested well. So there are about 1000 test cases to be written. The functionality is basically input/output. I have 1000 input Strings and want to test that after rewriting the component it behaves exactly the same.

Therefore I would like to have each object implement #toString() so that it creates Java source to create itself. Then I can pass my 1000 Strings and let the current implementation write the test case to ensure the behaviour of the component.

Bertram Nudelbach
  • 1,783
  • 2
  • 15
  • 27
  • So basically you want an API to clone arbitrary primitives and objects? – Carcigenicate Jul 21 '15 at 12:38
  • that's not quite the way of how Java is meant to work (?!) - what is your use case for bypassing the compiler and "magically" generating code structures (and compile them somehow) at runtime? – MWiesner Jul 21 '15 at 12:39
  • @MWiesner I don't want to bypass the compiler. I'm about to rewrite a component that isn't tested well. So there are about 1000 test cases to be written. The functionality is basically input/output. I have 1000 input Strings and want to test that after rewriting the component it behaves exactly the same. Therefore I would like to have each object implement #toString() so that it creates Java source to create itself. Then I can pass my 1000 Strings and let the current implementation write the test case to ensure the behaviour :) – Bertram Nudelbach Jul 21 '15 at 12:43
  • 1
    Why not give that information in the original question, as it helps others to understand your problem much better :) – MWiesner Jul 21 '15 at 12:44
  • 1
    @MWiesner thanks for helping to make the question more precise :) – Bertram Nudelbach Jul 21 '15 at 12:56
  • How should the function know if `Condition` has a method `setNumber(int number)`? You could have set the number by the constructor or by directly accessing it (`c1.number = 2;`) or in any other way. – halloei Jul 21 '15 at 13:08
  • @halloei in short, I don't know and that's why I need a library doing that thing for me :P Of course it would be a first step to require public fields or having a constructor setting all fields or something like that. But that's what the lib should require. My wish would be the magic lib that can do all that stuff without requiring me to prepare the objects first ;-) – Bertram Nudelbach Jul 21 '15 at 13:11
  • "have each object implement #toString()" => what "objects" are you talking about ? Don't you have access to the source code ? – ttzn Jul 21 '15 at 13:30
  • What about running the old implementation and the new one in parallel, and simply comparing the outputs with equals...? – lbalazscs Jul 21 '15 at 13:31
  • This cannot be done. You can't decompile .class files. And at runtime, bytecode is all you have. Use javap tool to get an idea of how your .class files look like, but don't expect it to be something wonderful. – fps Jul 21 '15 at 13:38
  • @Amine I have access to the source code and could simply implement #toString so that it works for the couple of classes that I have. But I want to know if there's a library doing that generically without writing the whole stuff into `toString`. I mentioned `toString` as it's well known and the behaviour should be similar to using `toString`. – Bertram Nudelbach Jul 21 '15 at 13:46
  • @lbalazscs yep that's a good approach but then I have to keep the old implementation forever as the test infrastructure. Would be nice to get rid of the old implementation. – Bertram Nudelbach Jul 21 '15 at 13:49

3 Answers3

4

Code generation is fun! You can achieve what you need by using reflection, sadly, there is no MagicCode implemented already.

You need to use the introspection to read what kind of object and create it according.

You can use the Eclipse JDT API to create classes.

Generating a compilation unit

The easiest way to programmatically generate a compilation unit is to use IPackageFragment.createCompilationUnit. You specify the name and contents of the compilation unit. The compilation unit is created inside the package and the new ICompilationUnit is returned.

From the docs, there is a example snippet.

So you basically will introspect to see what kind of object is and what is their fields and current values. Then you will use this API do create a corresponding AST. Your example might look like this.

public class CodeGenerator {

    public static void main(String[] args) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Foo foobar = new Foo();

        Bar bar = new Bar();
        bar.setSomeValue(555d);
        foobar.setBar(bar);
        foobar.setPrimitiveDouble(23);
        foobar.setValue("Hello World!");

        CodeGenerator codeGenerator = new CodeGenerator();

        String generatedCode = codeGenerator.generateCode(foobar);

        System.out.println(generatedCode);

    }

    int counter = 0;

    private String createVariableName(String clazzName) {
        return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, clazzName + getCurrentCounter());
    }

    public String generateCode(AST ast, List statements, Object object) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        String clazzName = object.getClass().getSimpleName();
        String variableName = createVariableName(clazzName);

        VariableDeclarationFragment newVariable = ast.newVariableDeclarationFragment();
        newVariable.setName(ast.newSimpleName(variableName)); // Or clazzName.toCamelCase()

        ClassInstanceCreation newInstance = ast.newClassInstanceCreation();
        newInstance.setType(ast.newSimpleType(ast.newSimpleName(clazzName)));
        newVariable.setInitializer(newInstance);

        VariableDeclarationStatement newObjectStatement = ast.newVariableDeclarationStatement(newVariable);
        newObjectStatement.setType(ast.newSimpleType(ast.newSimpleName(clazzName)));

        statements.add(newObjectStatement);

        BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
        for (PropertyDescriptor propertyDesc : beanInfo.getPropertyDescriptors()) {
            String propertyName = propertyDesc.getName();

            if (!shouldIgnore(propertyName)) {

                MethodInvocation setterInvocation = ast.newMethodInvocation();

                SimpleName setterName = ast.newSimpleName(propertyDesc.getWriteMethod().getName());
                setterInvocation.setName(setterName);

                Object invoked = propertyDesc.getReadMethod().invoke(object);

                if (invoked == null) {
                    continue;
                }

                if (Primitives.isWrapperType(invoked.getClass())) {
                    if (Number.class.isAssignableFrom(invoked.getClass())) {
                        setterInvocation.arguments().add(ast.newNumberLiteral(invoked.toString()));
                    }

                    // TODO: Booleans
                } else {

                    if (invoked instanceof String) {
                        StringLiteral newStringLiteral = ast.newStringLiteral();
                        newStringLiteral.setLiteralValue(invoked.toString());
                        setterInvocation.arguments().add(newStringLiteral);
                    } else {

                        String newObjectVariable = generateCode(ast, statements, invoked);
                        SimpleName newSimpleName = ast.newSimpleName(newObjectVariable);
                        setterInvocation.arguments().add(newSimpleName);
                    }

                }

                SimpleName newSimpleName = ast.newSimpleName(variableName);
                setterInvocation.setExpression(newSimpleName);

                ExpressionStatement setterStatement = ast.newExpressionStatement(setterInvocation);

                statements.add(setterStatement);

            }

        }

        return variableName;
    }

    public String generateCode(Object object) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        resetCounter();

        AST ast = AST.newAST(AST.JLS3);
        Block block = ast.newBlock();

        generateCode(ast, block.statements(), object);

        return block.toString();

    }

    private int getCurrentCounter() {
        return counter++;
    }

    private void resetCounter() {
        counter = 0;
    }

    private boolean shouldIgnore(String propertyName) {
        return "class".equals(propertyName);
    }
}

The dependencies I used:

    <dependency>
        <groupId>org.eclipse.tycho</groupId>
        <artifactId>org.eclipse.jdt.core</artifactId>
        <version>3.9.1.v20130905-0837</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.core</groupId>
        <artifactId>runtime</artifactId>
        <version>3.9.100-v20131218-1515</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.birt.runtime</groupId>
        <artifactId>org.eclipse.core.resources</artifactId>
        <version>3.8.101.v20130717-0806</version>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>18.0</version>
    </dependency>

This is what the output looks like :

  Foo foo0=new Foo();
  Bar bar1=new Bar();
  bar1.setSomeValue(555.0);
  foo0.setBar(bar1);
  foo0.setPrimitiveDouble(23.0);
  foo0.setValue("Hello World!");

Here is the Foo and Bar class declaration:

public class Bar {

private double someValue;

public double getSomeValue() {
    return someValue;
}

public void setSomeValue(double someValue) {
    this.someValue = someValue;
}

}

public class Foo {

private String value;
private double primitiveDouble;
private Bar bar;

public Bar getBar() {
    return bar;
}

public double getPrimitiveDouble() {
    return primitiveDouble;
}

public String getValue() {
    return value;
}

public void setBar(Bar bar) {
    this.bar = bar;
}

public void setPrimitiveDouble(double primitiveDouble) {
    this.primitiveDouble = primitiveDouble;
}

public void setValue(String value) {
        this.value = value;
    }
}

I added this to a github repository as requested.

André
  • 2,184
  • 1
  • 22
  • 30
  • I'm aware the code is.. bad, but it is just a proof of concept. I hope I understood the problem corectly – André Jul 21 '15 at 16:51
  • Don't worry the code style - I get the point :) Very nice. So now let's put it on github and you've created a wonderful library! Afais the limitation is that there needs to be a setter for each field (`propertyDesc.getWriteMethod()`)? Are there other limitations as far as you can see? – Bertram Nudelbach Jul 22 '15 at 08:02
  • Also inheritance, I don't traverse the super class too. Or primitives. – André Jul 22 '15 at 11:22
  • And a default constructor must be available, right? – Bertram Nudelbach Jul 22 '15 at 11:25
  • @BertramNudelbach, it's online there, have fun (Link in the answer) – André Jul 22 '15 at 11:35
1

You don't need the generated source code for testing. If all of your classes have toString methods that represent the whole relevant internal state of your objects, you can still automatically generate the test cases: they will compare strings with strings.

There are lots of ways to automatically generate good toString methods, for example Intellij Idea offers 9 templates.

lbalazscs
  • 17,474
  • 7
  • 42
  • 50
  • Thanks, that would work. Even if toString() is implemented badly you could add another method for that purpose! But a code generating lib would be fancy and thus I'll wait a few days until accepting an alternative answer ;-) – Bertram Nudelbach Jul 21 '15 at 15:46
  • 1
    Yes, it would be cool to see such a code generation lib, but I think that in this case toString or a similar method could be even better than the source code, because it can be optimized to contain only the relevant information, while the test with source code would be more fragile: you might need to update the test cases even after a change that does not affect the relevant internal state. – lbalazscs Jul 21 '15 at 16:20
-2

If you have a String that contains a source code and wish to compile it at run time, there is the Java Compiler API for this purpose

Sharon Ben Asher
  • 13,849
  • 5
  • 33
  • 47
  • Thanks but I don't have a String with the source code. I have an object at runtime and want the String that contains the source code. I basically don't want to type the whole test by my own but making my SUT ready to write the test. – Bertram Nudelbach Jul 21 '15 at 13:03