2011-11-03 15 views
24

ho trovato molti riferimenti che spiegano come compilare programmazione una classe Java utilizzando la classe JavaCompiler:È possibile compilare in modo programmatico il codice sorgente java solo in memoria?

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
int result = compiler.run(null, null, null, "a_file_name"); 

Tuttavia, vorrei sapere se v'è una libreria open source che mi permetta di compilare il codice sorgente generato a livello di codice (quindi senza che sia coinvolto un file src) e generi un codice byte in un flusso di output (senza generare un file di classe nel file system).

Per esempio, sto cercando di essere in grado di scrivere qualcosa del genere:

InputStream input = generateSourceCode(); 
OutputStream output = getByteCode(input); 
doCoolStuffWithByteCode(output); 

Grazie per qualsiasi aiuto.

+0

Vedere il [Compilatore basato su testo ** SSCCE ** (http://pscode.org/stbc/) per una dimostrazione. di ciò a cui James e Brian si riferiscono. L'STBC usa 'JavaCompiler' /' SimpleJavaFileObject'. –

risposta

38

Per iniziare, guarda il JavaCompiler API. Fondamentalmente:

  1. Creare la classe Java in una stringa.
  2. Inserire la stringa in classe che si estende SimpleJavaFileObject.
  3. Compilare utilizzando un'istanza JavaCompiler.

Infine, chiamare i metodi della nuova classe.


Ecco un example che funziona con JDK6 +:

import java.io.IOException; 
import java.io.PrintWriter; 
import java.io.StringWriter; 
import java.lang.reflect.InvocationTargetException; 
import java.net.URI; 
import java.util.Arrays; 

import javax.tools.Diagnostic; 
import javax.tools.DiagnosticCollector; 
import javax.tools.JavaCompiler; 
import javax.tools.JavaFileObject; 
import javax.tools.SimpleJavaFileObject; 
import javax.tools.ToolProvider; 
import javax.tools.JavaCompiler.CompilationTask; 
import javax.tools.JavaFileObject.Kind; 

public class CompileSourceInMemory { 
    public static void main(String args[]) throws IOException { 
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); 

    StringWriter writer = new StringWriter(); 
    PrintWriter out = new PrintWriter(writer); 
    out.println("public class HelloWorld {"); 
    out.println(" public static void main(String args[]) {"); 
    out.println(" System.out.println(\"This is in another java file\");");  
    out.println(" }"); 
    out.println("}"); 
    out.close(); 
    JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString()); 

    Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file); 
    CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits); 

    boolean success = task.call(); 
    for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { 
     System.out.println(diagnostic.getCode()); 
     System.out.println(diagnostic.getKind()); 
     System.out.println(diagnostic.getPosition()); 
     System.out.println(diagnostic.getStartPosition()); 
     System.out.println(diagnostic.getEndPosition()); 
     System.out.println(diagnostic.getSource()); 
     System.out.println(diagnostic.getMessage(null)); 

    } 
    System.out.println("Success: " + success); 

    if (success) { 
     try { 
     Class.forName("HelloWorld").getDeclaredMethod("main", new Class[] { String[].class }) 
      .invoke(null, new Object[] { null }); 
     } catch (ClassNotFoundException e) { 
     System.err.println("Class not found: " + e); 
     } catch (NoSuchMethodException e) { 
     System.err.println("No such method: " + e); 
     } catch (IllegalAccessException e) { 
     System.err.println("Illegal access: " + e); 
     } catch (InvocationTargetException e) { 
     System.err.println("Invocation target: " + e); 
     } 
    } 
    } 
} 

class JavaSourceFromString extends SimpleJavaFileObject { 
    final String code; 

    JavaSourceFromString(String name, String code) { 
    super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE); 
    this.code = code; 
    } 

    @Override 
    public CharSequence getCharContent(boolean ignoreEncodingErrors) { 
    return code; 
    } 
} 
+0

+1 Grazie mille per i suggerimenti. L'ultimo link mostra quasi quello che sto cercando. L'unica differenza è che apparentemente richiede di conoscere il nome della classe da compilare in anticipo, e l'unica cosa che ho è il suo codice sorgente completo. – Sergio

+0

Puoi semplicemente cercare nella classe la parola "public class" e la parola successiva sarà probabilmente il nome della classe. –

+0

So che posso scansionare manualmente il codice sorgente, chiedendomi solo se qualcosa di più elegante potrebbe essere fatto. Grazie ! – Sergio

0

Abbiamo dato un parlare di questo caso d'uso a JavaOne 2016 (la questione è un po 'vecchio, ma sembra che ci sia un certo interesse ancora).

C'è un repository con esempi di generazione di codice pratico utilizzando javac in-memory.

Specificare in particolare SimpleJavaCompiler per un esempio su come eseguire questa operazione in memoria che riguarda la sicurezza dei thread (la usiamo nel contesto di un server) per una singola classe. Potrebbe facilmente essere adattato per uno scenario multi-classe.

Esistono anche classi per il caricamento della classe e la generazione del codice (definizione di variabili, generazione di nomi univoci, ombreggiatura del nome, ecc.).

Problemi correlati