是否有一个Java库可以生成Java源代码来创建对象?

时间:2015-07-21 12:35:56

标签: java generated-code

我正在寻找一个能够生成源代码的API,它会生成与我传递给库的对象相同的对象。

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

应该生成

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

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

所以我想传递一个Object并获得 Java源代码,这将创建一个等于我的初始Object的对象。

这不仅适用于原始类型,也适用于我自己的对象:

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

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

应该生成

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

as String,然后可以粘贴到Java Source文件。

修改

我不想绕过编译器。

我要重写一个未经过良好测试的组件。因此,大约需要编写1000个测试用例。功能基本上是输入/输出。我有1000个输入字符串,并希望在重写组件后测试它,它的行为完全相同。

因此,我希望每个对象都实现#toString(),以便创建Java源来创建自己。然后我可以传递我的1000个字符串,让当前的实现编写测试用例以确保组件的行为。

3 个答案:

答案 0 :(得分:3)

代码生成很有趣!您可以通过使用反射来实现所需,遗憾的是,已经没有实现MagicCode

您需要使用内省来读取什么类型的对象并根据它创建它。

您可以使用 Eclipse JDT API 来创建类。

  

生成编译单元

     

以编程方式生成编译单元的最简单方法是使用IPackageFragment.createCompilationUnit。您指定编译单元的名称和内容。编译单元在包内创建,并返回新的ICompilationUnit。

docs开始,有一个示例代码段。

所以你基本上会反省看什么样的对象以及它们的字段和当前值是什么。然后你将使用这个API做一个相应的AST。您的示例可能如下所示。

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

我使用的依赖项:

    <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>

这是输出的样子:

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

这是Foo和Bar类声明:

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;
    }
}

我已根据要求将此添加到github repository

答案 1 :(得分:1)

您不需要生成的源代码进行测试。如果所有类都有toString方法来表示对象的整个相关内部状态,您仍然可以自动生成测试用例:它们会将字符串与字符串进行比较。

有很多方法可以自动生成好的toString方法,例如Intellij Idea提供了9个模板。

答案 2 :(得分:-2)

如果你有一个包含源代码并希望在运行时编译它的String,那么就有Java Compiler API用于此目的