2010-11-29 5 views
1

Ich verwende Java Compiler-API zum Kompilieren von In-Memory-Klassen. Das heißt, Klassen werden zu Bytecode kompiliert (keine .classes-Dateien, die auf der Platte gespeichert sind) und dann durch Rekonstruieren des Bytecodes geladen.Java-Compiler-API mit Klassen, die voneinander abhängen

Manchmal muss ich eine Klasse kompilieren, die von einer anderen, ebenfalls in Arbeitsspeicher kompilierten Klasse abhängt. Zum Beispiel: Kompilieren Sie Klasse A und kompilieren Sie dann Klasse B, die von Klasse A abhängt.

Um dies zu lösen, überlasse ich Klasse A und Klasse B als die von der getTask-Methode der Compiler-API benötigten Kompilierungseinheiten.

Allerdings mag ich diese Lösung nicht wirklich, da es mich kompiliert Klasse A, die bereits kompiliert wurde.

Gibt es eine Möglichkeit, dies zu umgehen?

EDIT: Ich fand eine Lösung über diesen Link: http://www.ibm.com/developerworks/java/library/j-jcomp/index.html

+0

Entschuldigung, wenn ich langsam bin, aber ich sehe nicht, wie der Link, den Sie gepostet haben, das Problem löst. Ich bin selbst auf dasselbe Problem gestoßen ... meine API ist viel sauberer und einfacher zu verwalten, wenn ich Klassen separat kompilieren kann. Ich würde es sehr schätzen, wenn Sie die Lösung als Antwort posten könnten. :) – stevevls

+1

eigentlich, ich habe gerade das funktioniert, also habe ich eine Antwort für die Nachwelt geschrieben. Danke für die tolle Frage! – stevevls

Antwort

1

, die auf die offensichtliche Frage führt, warum Sie wollen separat erste Klasse A kompilieren. Warum nicht alles auf einmal zusammenstellen?

+0

Ich denke, du verpasst den Punkt. Ich habe ein Beispiel gepostet. Ihre Lösung ist nicht erweiterbar oder modular. – halfwarp

+0

@halfwarp: Wenn ich den Punkt verpasse, deutet das vielleicht darauf hin, dass Sie nicht genug Informationen geben? Sie haben weder erklärt, was Sie erreichen möchten, noch welche Erweiterbarkeit oder Modularität Sie benötigen. –

0

Wie, wenn Sie die geänderte Zeit der Dateien und des (in Arbeitsspeicher) kompilierten Bytecodes beibehalten?

0

Ich glaube nicht, dass Sie vermeiden können, beide Klassen zu kompilieren. Wenn Sie beide nicht kompilieren, besteht die Möglichkeit, dass Sie Probleme mit binären Kompatibilitäten oder Probleme mit falschen inline-Konstanten haben.

Dies ist im Wesentlichen das gleiche Problem wie Sie erhalten würden, wenn Sie eine Klasse und nicht die andere über die Befehlszeile kompiliert.

Aber um ehrlich zu sein, würde ich mich nicht darum kümmern, die Zusammenstellung so zu optimieren. (Und wenn Ihre Anwendung muss in der Lage sein, dynamisch eine Klasse zu kompilieren und das andere nicht, hat es wahrscheinlich hat bedeutende Design-Fragen.)

2

Ja, dies durchaus möglich ist, solange Sie richtig die ForwardingJavaFileManager implementieren. Die zwei wichtigsten Methoden sind inferBinaryName() und list(). Wenn Sie diese beiden richtig einrichten, kann der Compiler Klassen auflösen, die Sie zuvor kompiliert haben.

inferBinaryName() muss die Klasse zurückkehren simple name (zum Beispiel die abgeleitete binäre Namen für com.test.Test wäre nur Test). Hier ist meine Implementierung (meine Unterklasse von JavaFileObject genannt wird InAppJavaFileObject):

@Override 
public String inferBinaryName(Location location, JavaFileObject javaFileObject) { 

    if(location == StandardLocation.CLASS_PATH && javaFileObject instanceof InAppJavaFileObject) { 
     return StringUtils.substringBeforeLast(javaFileObject.getName(), ".java"); 
    } 

    return super.inferBinaryName(location, javaFileObject); 
} 

Bitte beachte, dass ich Abstreifen „.java“ vom Ende. Wenn Sie einen JavaFileObject erstellen, muss der Dateiname in ".java" enden, aber wenn Sie das Suffix später nicht entfernen, findet der Compiler Ihre Klasse nicht.

list() ist ein bisschen komplizierter, weil Sie vorsichtig sein müssen, um gut mit Ihrem Delegaten Dateimanager zu spielen. In meiner Implementierung, halte ich eine Karte voll qualifizierten Klassennamen in meine Unterklasse von JavaFileObject, die ich durchlaufen kann:

@Override 
public Iterable<JavaFileObject> list(Location action, String pkg, Set<JavaFileObject.Kind> kind, boolean recurse) throws IOException { 

    Iterable<JavaFileObject> superFiles = super.list(action, pkg, kind, recurse); 

    // see if there's anything in our cache that matches the criteria. 
    if(action == StandardLocation.CLASS_PATH && (kind.contains(JavaFileObject.Kind.CLASS) || kind.contains(JavaFileObject.Kind.SOURCE))) { 

     List<JavaFileObject> ourFiles = new ArrayList<JavaFileObject>(); 
     for(Map.Entry<String,InAppJavaFileObject> entry : files.entrySet()) { 
      String className = entry.getKey(); 
      if(className.startsWith(pkg) && ("".equals(pkg) || pkg.equals(className.substring(0, className.lastIndexOf('.'))))) { 
       ourFiles.add(entry.getValue()); 
      } 
     } 

     if(ourFiles.size() > 0) { 
      for(JavaFileObject javaFileObject : superFiles) { 
       ourFiles.add(javaFileObject); 
      } 

      return ourFiles; 
     } 
    } 

    // nothing found in our hash map that matches the criteria... return 
    // whatever super came up with. 
    return superFiles; 
} 

Sobald Sie diese Methoden ordnungsgemäß umgesetzt haben, funktioniert der Rest nur. Genießen!

+0

Der einzige Vorbehalt hier ist, dass Sie in jedem benutzerdefinierten JavaFileObject, das Sie verwenden (in Ihrem Beispiel "InAppJavaFileObject"), OpenInputStream() implementieren müssen, um alle Bytes in dem Stream in openOutputStream() zurückgegeben zurückgegeben, wenn Sie so speichern Klassendaten während der Kompilierung. Aber es hat perfekt für mich funktioniert, danke! – Carrotman42

Verwandte Themen