4

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.

12
  • So basically you want an API to clone arbitrary primitives and objects? Commented Jul 21, 2015 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? Commented Jul 21, 2015 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 :) Commented Jul 21, 2015 at 12:43
  • 1
    Why not give that information in the original question, as it helps others to understand your problem much better :) Commented Jul 21, 2015 at 12:44
  • 1
    @MWiesner thanks for helping to make the question more precise :) Commented Jul 21, 2015 at 12:56

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

5 Comments

I'm aware the code is.. bad, but it is just a proof of concept. I hope I understood the problem corectly
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?
Also inheritance, I don't traverse the super class too. Or primitives.
And a default constructor must be available, right?
@BertramNudelbach, it's online there, have fun (Link in the answer)
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.

2 Comments

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

1 Comment

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.