Uma API Java para gerar arquivos de origem Java [fechados]

127

Estou procurando uma estrutura para gerar arquivos de origem Java.

Algo como a seguinte API:

X clazz = Something.createClass("package name", "class name");
clazz.addSuperInterface("interface name");
clazz.addMethod("method name", returnType, argumentTypes, ...);

File targetDir = ...;
clazz.generate(targetDir);

Em seguida, um arquivo de origem java deve ser encontrado em um subdiretório do diretório de destino.

Alguém conhece essa estrutura?


EDIT :

  1. Eu realmente preciso dos arquivos de origem.
  2. Eu também gostaria de preencher o código dos métodos.
  3. Estou procurando uma abstração de alto nível, não manipulação / geração direta de bytecode.
  4. Eu também preciso da "estrutura da classe" em uma árvore de objetos.
  5. O domínio do problema é geral: gerar uma grande quantidade de classes muito diferentes, sem uma "estrutura comum".

SOLUÇÕES
Postei 2 respostas com base em suas respostas ... com o CodeModel e com o Eclipse JDT .

Eu usei o CodeModel na minha solução: :-)

Daniel Fanjul
fonte
Sua pergunta é muito geral. O domínio do seu problema é realmente esse geral? Você pode ser mais específico sobre o domínio do seu problema? Por exemplo, eu escrevi ferramentas de geração de código para gerar código para problemas específicos, como eliminar o código da classe de exceção duplicada ou eliminar a enumeração.
Greg Mattes
@ Lookward: Você pode mover as respostas que colocou na pergunta como 2 respostas separadas abaixo. Em seguida, adicione um link para cada um da pergunta.
Ande Turner 25/09/08
@ Banengusk: Obrigado por perguntar, me salvou horas pesquisando as partes mais sombrias da internet. @skaffman: Great encontrar - você fez outro desenvolvedor mais à vontade com a sua próxima tarefa :)
Ran Biron
Esta resposta SO aborda a questão para C ++ em vez de Java, mas a resposta também funciona para Java. stackoverflow.com/a/28103779/120163
Ira Baxter

Respostas:

70

A Sun fornece uma API chamada CodeModel para gerar arquivos de origem Java usando uma API. Não é a coisa mais fácil de obter informações, mas existe e funciona muito bem.

A maneira mais fácil de se apossar dele é como parte do JAXB 2 RI - o gerador de esquema para java XJC usa o CodeModel para gerar sua fonte java e faz parte dos jars XJC. Você pode usá-lo apenas para o CodeModel.

Pegue-o em http://codemodel.java.net/

skaffman
fonte
2
É exatamente o que eu preciso! Simples e totalmente funcional. Obrigado, skaffman!
Daniel Fanjul 23/09/08
1
@BradCupit De acordo com o arquivo pom repo.maven.apache.org/maven2/com/sun/codemodel/codemodel/2.6/… , é CDDL + GPL glassfish.java.net/public/CDDL+GPL_1_1.html
ykaganovich
@ykaganovich Good call. É [ repo.maven.apache.org/maven2/com/sun/codemodel/… licenciado sob CDDL e GPL). Eu removi meu comentário anterior.
precisa saber é o seguinte
46

Solução encontrada com o CodeModel
Obrigado, skaffman .

Por exemplo, com este código:

JCodeModel cm = new JCodeModel();
JDefinedClass dc = cm._class("foo.Bar");
JMethod m = dc.method(0, int.class, "foo");
m.body()._return(JExpr.lit(5));

File file = new File("./target/classes");
file.mkdirs();
cm.build(file);

Eu posso obter esta saída:

package foo;
public class Bar {
    int foo() {
        return  5;
    }
}
Daniel Fanjul
fonte
Isso parece incrível. Como você gera um método que retorna outro tipo que está sendo gerado com o CodeModel também?
András Hummer
@DrH, simples pesquisa google: codemodel.java.net/nonav/apidocs/com/sun/codemodel/...
Daniel Fanjul
@ AndrásHummer usa a instância retornada cm._class(...)como argumento de tipo de retorno para dc.method(...).
Hugo Baés 21/01
28

Solução encontrada com o AST do Eclipse JDT
Obrigado, Giles .

Por exemplo, com este código:

AST ast = AST.newAST(AST.JLS3);
CompilationUnit cu = ast.newCompilationUnit();

PackageDeclaration p1 = ast.newPackageDeclaration();
p1.setName(ast.newSimpleName("foo"));
cu.setPackage(p1);

ImportDeclaration id = ast.newImportDeclaration();
id.setName(ast.newName(new String[] { "java", "util", "Set" }));
cu.imports().add(id);

TypeDeclaration td = ast.newTypeDeclaration();
td.setName(ast.newSimpleName("Foo"));
TypeParameter tp = ast.newTypeParameter();
tp.setName(ast.newSimpleName("X"));
td.typeParameters().add(tp);
cu.types().add(td);

MethodDeclaration md = ast.newMethodDeclaration();
td.bodyDeclarations().add(md);

Block block = ast.newBlock();
md.setBody(block);

MethodInvocation mi = ast.newMethodInvocation();
mi.setName(ast.newSimpleName("x"));

ExpressionStatement e = ast.newExpressionStatement(mi);
block.statements().add(e);

System.out.println(cu);

Eu posso obter esta saída:

package foo;
import java.util.Set;
class Foo<X> {
  void MISSING(){
    x();
  }
}
Daniel Fanjul
fonte
Posso perguntar - você fez isso como parte de um Java Eclipse Plugin ou conseguiu usar isso como código independente? Eu sei que isso tem anos.
MTRC
@ mtrc Se bem me lembro, era um projeto java independente e normal no eclipse, adicionando o jar apropriado ao caminho de classe - mas não me lembro do nome do arquivo.
precisa
17

Você pode usar o Roaster ( https://github.com/forge/roaster ) para gerar o código.

Aqui está um exemplo:

JavaClassSource source = Roaster.create(JavaClassSource.class);
source.setName("MyClass").setPublic();
source.addMethod().setName("testMethod").setPrivate().setBody("return null;")
           .setReturnType(String.class).addAnnotation(MyAnnotation.class);
System.out.println(source);

exibirá a seguinte saída:

public class MyClass {
   private String testMethod() {
       return null;
   }
}
gastaldi
fonte
9

Outra alternativa é o AST do Eclipse JDT, que é bom se você precisar reescrever o código-fonte Java arbitrário em vez de apenas gerar o código-fonte. (e acredito que possa ser usado independentemente do eclipse).

Esquilo
fonte
1
Ótimo!! Uma árvore de sintaxe abstrata é o que eu estou procurando ... Agora vou procurar mais informações sobre a API ... Obrigado !, :-)
Daniel Fanjul
A API é complexa, como eu esperava. Mas tem toda a funcionalidade que eu preciso. Obrigado, Giles.
Daniel Fanjul 23/09/08
1
Conforme mencionado por @gastaldi, o roaster (do JBoss Forge) é um bom invólucro para o Eclipse JDT. Ele oculta a complexidade do JDT e fornece uma API agradável para analisar, modificar ou escrever código java. github.com/forge/roaster
Jmini 24/10
4

O projeto Eclipse JET pode ser usado para gerar a fonte. Não acho que a API seja exatamente igual à que você descreveu, mas toda vez que ouvi falar de um projeto que gera geração de código-fonte Java, eles usaram o JET ou uma ferramenta caseira.

Mike Deck
fonte
3

Não conhece uma biblioteca, mas um mecanismo de modelo genérico pode ser tudo o que você precisa. Existem vários deles , eu pessoalmente tive uma boa experiência com o FreeMarker

ykaganovich
fonte
2

Eu criei algo que se parece muito com o seu DSL teórico, chamado "sourcegen", mas tecnicamente em vez de um projeto util para um ORM que escrevi. O DSL se parece com:

@Test
public void testTwoMethods() {
    GClass gc = new GClass("foo.bar.Foo");

    GMethod hello = gc.getMethod("hello");
    hello.arguments("String foo");
    hello.setBody("return 'Hi' + foo;");

    GMethod goodbye = gc.getMethod("goodbye");
    goodbye.arguments("String foo");
    goodbye.setBody("return 'Bye' + foo;");

    Assert.assertEquals(
    Join.lines(new Object[] {
        "package foo.bar;",
        "",
        "public class Foo {",
        "",
        "    public void hello(String foo) {",
        "        return \"Hi\" + foo;",
        "    }",
        "",
        "    public void goodbye(String foo) {",
        "        return \"Bye\" + foo;",
        "    }",
        "",
        "}",
        "" }),
    gc.toCode());
}

https://github.com/stephenh/joist/blob/master/util/src/test/java/joist/sourcegen/GClassTest.java

Ele também faz algumas coisas legais como "Organizar automaticamente importações" quaisquer FQCNs em parâmetros / tipos de retorno, removendo automaticamente todos os arquivos antigos que não foram tocados nessa execução de codegen, recuando corretamente as classes internas, etc.

A idéia é que o código gerado seja bonito de se ver, sem avisos (importações não usadas etc.), assim como o restante do código. Tanto código gerado é feio de ler ... é horrível.

De qualquer forma, não há muitos documentos, mas acho que a API é bem simples / intuitiva. O repo Maven está aqui se alguém estiver interessado.

Stephen Haberman
fonte
1

Se você realmente precisa da fonte, não sei de nada que gere a fonte. No entanto, você pode usar o ASM ou o CGLIB para criar diretamente os arquivos .class.

Você pode gerar origem a partir deles, mas eu os usei apenas para gerar bytecode.

Steve g
fonte
1

Eu mesmo estava fazendo isso por uma ferramenta de gerador simulado. É uma tarefa muito simples, mesmo se você precisar seguir as diretrizes de formatação da Sun. Aposto que você terminaria o código que faz isso mais rapidamente do que você encontrou algo que se encaixa no seu objetivo na Internet.

Você basicamente descreveu a API por conta própria. Basta preenchê-lo com o código real agora!

Vladimir Dyuzhev
fonte
Hehehe ... Se nenhum framework for encontrado, eu vou escrever. Eu gostaria de um monte de funcionalidade por isso não vou obtê-lo em uma manhã ...
Daniel Fanjul
1

Há um novo projeto write-it-once . Gerador de código baseado em modelo. Você escreve um modelo personalizado usando o Groovy e gera um arquivo dependendo das reflexões java. É a maneira mais simples de gerar qualquer arquivo. Você pode criar getters / settest / toString gerando arquivos AspectJ, SQL com base em anotações JPA, inserções / atualizações com base em enumerações e assim por diante.

Exemplo de modelo:

package ${cls.package.name};

public class ${cls.shortName}Builder {

    public static ${cls.name}Builder builder() {
        return new ${cls.name}Builder();
    }
<% for(field in cls.fields) {%>
    private ${field.type.name} ${field.name};
<% } %>
<% for(field in cls.fields) {%>
    public ${cls.name}Builder ${field.name}(${field.type.name} ${field.name}) {
        this.${field.name} = ${field.name};
        return this;
    }
<% } %>
    public ${cls.name} build() {
        final ${cls.name} data = new ${cls.name}();
<% for(field in cls.fields) {%>
        data.${field.setter.name}(this.${field.name});
<% } %>
        return data;
    }
}
Atmega
fonte
0

Realmente depende do que você está tentando fazer. A geração de código é um tópico em si. Sem um caso de uso específico, sugiro examinar a biblioteca de geração / modelo de código de velocidade. Além disso, se você estiver gerando código offline, sugiro usar algo como ArgoUML para passar do diagrama UML / modelo de objeto para o código Java.

Berlin Brown
fonte
0

Exemplo: 1 /

private JFieldVar generatedField;

2 /

String className = "class name";
        /* package name */
        JPackage jp = jCodeModel._package("package name ");
         /*  class name  */
        JDefinedClass jclass = jp._class(className);
        /* add comment */
        JDocComment jDocComment = jclass.javadoc();
        jDocComment.add("By AUTOMAT D.I.T tools : " + new Date() +" => " + className);
        // génération des getter & setter & attribues

            // create attribue 
             this.generatedField = jclass.field(JMod.PRIVATE, Integer.class) 
                     , "attribue name ");
             // getter
             JMethod getter = jclass.method(JMod.PUBLIC, Integer.class) 
                     , "attribue name ");
             getter.body()._return(this.generatedField);
             // setter
             JMethod setter = jclass.method(JMod.PUBLIC, Integer.class) 
                     ,"attribue name ");
             // create setter paramétre 
             JVar setParam = setter.param(getTypeDetailsForCodeModel(Integer.class,"param name");
             // affectation  ( this.param = setParam ) 
             setter.body().assign(JExpr._this().ref(this.generatedField), setParam);

        jCodeModel.build(new File("path c://javaSrc//"));
user3207181
fonte