Sie sind hier : sebastian1012.bplaced.net/ homepage-neu / informationen / Dynamische_Uebersetzung_und_Skriptsprachen.php

19 Dynamische Übersetzung und SkriptsprachenZur nächsten Überschrift

19 Dynamische Übersetzung und SkriptsprachenZur nächsten Überschrift

»Erfolg sollte stets nur die Folge, nie das Ziel des Handelns sein.«
– Gustave Flaubert (1821–1880)

Für Java-Entwickler besteht der Alltag im Allgemeinen darin, neuen Java-Quellcode zu schreiben und eigenen oder fremden Quellcode zu lesen, zu verstehen und zu ändern. Nun ist Java als Programmiersprache aber nicht immer die effizienteste Darstellungsform für einen Algorithmus oder ein Geschäftsproblem. Es gibt je nach Anwendungsfall kompaktere und bessere Ausdrucksformen. So kann eine grafische Notation für gewisse Problemstellungen sinnvoller sein als eine Gruppe von Java-Klassen, oder Tests können vielleicht besser in natürlicher Sprache formuliert werden. Installer oder Dialoge könnten alternativ in XML beschrieben werden. Besonders Skriptsprachen haben deutlich kompaktere Ausdrucksformen als Java. Ein Trend sind sogenannte Domain Specific Languages (DSL). Das sind Spezial-Sprachen, die nur für ein ganz gewisses Problem definiert wurden und optimal ein Problem lösen sollen. So ist PDF eine optimale DSL für die Beschreibung von zweidimensionalen Grafiken, und Shell-Skripte sind perfekt zum Automatisieren von Vorgängen. Die Lösung hätte durchaus mit Java formuliert werden können, jedoch ist Java hierzu nicht optimal geeignet. Java ist als universell einsetzbare Programmiersprache eher das Gegenteil einer DSL. Und das Ziel sollte immer sein, das beste Werkzeug für eine Aufgabe zu wählen und viel zu automatisieren.

Da unabhängig von der Darstellungsform aber letztendlich eine Java-Laufzeitumgebung das Programm abarbeiten muss, stellt sich die Frage, wie die unterschiedlichen Darstellungsformen wie eine DSL ausgeführt werden, denn die JVM versteht nur Bytecode. Hier bieten sich zwei Wege an:

In diesem Kapitel schauen wir uns genau diese Bereiche an. Zunächst wollen wir sehen, welche Möglichkeiten es gibt, Java-Programmcode zu generieren. Ob vor dem Programmstart oder dynamisch ist dann unerheblich. Der zweite Schritt soll das Problem lösen, wie (generierter) Java-Programmcode in Java-Bytecode übersetzt wird. Der letzte Teil des Kapitels stellt Skriptsprachen vor, von denen einige rein interpretiert werden und andere zur Laufzeit Bytecode erstellen, sodass sie von den Optimierungsmöglichkeiten der JVM profitieren.


Rheinwerk Computing - Zum Seitenanfang

19.1 CodegenerierungZur nächsten ÜberschriftZur vorigen Überschrift

Der Ausgangspunkt der Codegenerierung ist ein Artefakt, das ein Übersetzer in Java-Quellcode oder Java-Bytecode überführt. Ob das Ergebnis Quellcode oder direkt Java-Bytecode ist, hat verschiedene Vor- und Nachteile.

Tabelle 19.1: Vor- und Nachteile vom Compiler und von direkter Bytecode-Erzeugung

 

Vorteil Nachteil
Generierung von Quellcode

Der Quellcode ist einsehbar. leichter zu debuggen Der Generator ist leicht zu schreiben.

Ein Compiler ist erforderlich. Die Übersetzung kostet Zeit und Speicher.

Direkte Bytecode-Generierung

sehr performant Es ist kein Java-Compiler nötig. sinnvoll bei Bytecode-zu-Bytecode-Transformationen

Der Bytecode ist schlecht einsehbar. Das Debugging ist schwierig. Der Generator ist schwer zu schreiben, da Wissen über Bytecode nötig ist.

Generatoren, die – aus welchem Zielformat auch immer – Java-Quellcode generieren, sind relativ einfach zu entwickeln. Der Aufwand besteht in der Entwicklung des Parsers, aber das Abbilden in einen Quellcode-String ist einfach. Ganz anders sieht es bei der Bytecodeausgabe aus. Zwar vereinfachen Bibliotheken das Generieren von Bytecode, doch bleibt eine deutlich höhere Komplexität bestehen. Der Vorteil ist jedoch, dass das Generieren und Verändern vom Bytecode recht performant ist und so problemlos zur Laufzeit vorgenommen werden kann. Sogar beim Laden der Klassen lassen sich Bytecodemodifizierungen vornehmen, und in der Laufzeitzeit schlägt sich das nicht auffällig negativ nieder.


Rheinwerk Computing - Zum Seitenanfang

19.1.1 Generierung von Java-QuellcodeZur nächsten ÜberschriftZur vorigen Überschrift

Wir haben gesehen, dass ein Generator entweder Java-Quellcode oder Java-Bytecode generieren kann. Soll die Ausgabe ein Java-Programm im Quellcode sein, so lassen sich unterschiedliche Techniken zur Erzeugung nutzen:

Tabelle 19.2: Möglichkeiten zur Erzeugung von Quellcode

Quellcode als Text Quellcode über abstrakte Repräsentation

Einfach

Template

 

Konkatenation von Strings oder Ausgaben über printXXX()

Ein Template enthält Platzhalter, die später gefüllt werden.

Es wird kein String erzeugt, sondern das Java-Programm wird über eine API aufgebaut.

Nach dem Aufbau kann eine String-Repräsentation erzeugt werden.

Generieren von Text

Im ersten Fall wird Java-Quellcode als String behandelt, der etwa über Konkatenation zusammengesetzt wird. Das kann so aussehen:

String generate( String classname, String output )
{
return "public class " +
classname +
"{ public static void main(String[] args){System.out.println(\"" +
output + "\");}}";
}

Der Nachteil ist offensichtlich und erinnert an Servlets, die HTML ausgeben: Das Programm selbst lässt sich kaum ändern, und der Programmautor verheddert sich schnell beim Escapen der Anführungsstriche. Besser ist es, das Java-Programm als Template zu nehmen und später Platzhalter mit den tatsächlichen Daten zu füllen. Die Java-Bibliothek bietet hier schon einen einfachen Mechanismus über String.format().

String generate( String classname, String output )
{
String template = "public class %1$s {" +
"public static void main(String[] args)" +
"{System.out.println(\"%2$s\");}}";
return String.format( template, classname, output );
}

Das Ganze können wir in eine Datei template.txt auslagern.

public class %1$s
{
public static void main( String[] args )
{
System.out.println( "%2$s" );
}
}

Steht die Template-Datei im Klassenpfad, so greift das folgende generate() nun auf die Datei zurück und füllt die Platzhalter.

String generate( String classname, String output )
{
String template = new Scanner(getClass().getResourceAsStream( "template.txt" ))
.useDelimiter( "\\Z" ).next();
return String.format( template, classname, output );
}

Fortgeschrittener ist es, statt Positionen Namen einzusetzen. Dann ist auch String.format() nicht mehr nötig, sondern ein einfaches replace() ersetzt Platzhalter.

public class %CLASSNAME%
{
public static void main( String[] args )
{
System.out.println( "%OUTPUT%" );
}
}

Und die generate()-Methode wird zu:

String generate( String classname, String output )
{
String template = new Scanner(getClass().getResourceAsStream( "template.txt" ))
.useDelimiter( "\\Z" ).next();
return template.replace( "%CLASSNAME%", classname).replace( "%OUTPUT%", output );
}

Das Ganze lässt sich noch generalisieren, indem statt der zwei Parameter eine Map an die Methode übergeben wird. Anstatt dann zwei feste replace()-Aurufe zu setzen, läuft eine Schleife über die Map und führt das Ersetzen durch, sodass der Schlüssel – Name des Platzhalters – durch den assoziierten Wert ersetzt wird.

Anstatt so etwas selbst zu programmieren, können wir auf vorgefertigte Lösungen zurückgreifen. Es bieten sich Template-Engines an, die neben einer einfachen Ersetzung noch viel mehr können, etwa Variablendeklarationen, Berechnungen, Schleifen, Makros. Bekannte Vertreter sind:

Quellcodegenerierung über Modelle

Das Generieren von Text ist sehr einfach, doch prinzipiell mit Fehlern behaftet. Es kann sein, dass Bezeichner ungültig sind, Typen nicht passen oder Sonderzeichen eines Strings nicht richtig ausmaskiert sind. Das Herausschreiben von Text ist über die Vorlage praktisch, doch kann eine Bibliothek zum Generieren von Java-Quellcode helfen:

Nachdem der Syntax-Baum aufgebaut wurde, kann er in eine Textrepräsentation überführt werden. Die Eclipse-API ist angenehm, doch natürlich voll an den Eclipse-Compiler gebunden. Sprachänderungen etwa für Java 7 kommen immer etwas verspätet. Die API von CodeModel ist nicht so komfortabel, und die Weiterentwicklung ist fragwürdig, da es lediglich als Unterprojekt von JAXB wirkt.

Fazit: Für ein einfaches Generieren von Quellcode sind die beiden APIs zu speziell. Templates sind sehr praktisch, wenn der Quellcode einem sehr festen Muster folgt, wie in unserem ersten Beispiel, in dem lediglich der Klassenname und ein String eingesetzt werden. Besser werden die API-Varianten, wenn der Quellcode weniger »frei« ist. Der größte Vorteil dieser Variante ist jedoch, dass sich der AST später einfacher modifizieren lässt. Auf der Basis von Quellcode ist das nicht möglich. Für Refactorings ist das optimal, aber um einfach Quellcode zu erzeugen, ist es etwas zu aufwändig.


Rheinwerk Computing - Zum Seitenanfang

19.1.2 CodetransformationenZur nächsten ÜberschriftZur vorigen Überschrift

Bisher haben wir uns darauf konzentriert, Java-Quellcode über ein Java-Programm zu generieren. Es geht aber auch anders, wenn etwa der Ausgang ein XML-Dokument mit einer Beschreibung ist. Dann kann mittels XSLT eine Transformation von XML in ein Java-Programm stattfinden. Allerdings sind solche Transformationen nicht besonderes angenehmen zu schreiben, doch möglich.

Randbemerkung

XMLVM (http://www.xmlvm.org/) ist ein spannendes Projekt, das Java-Bytecode zunächst in XML abbildet. Eine anschließende XSLT-Transformation kann dann den Bytecode in ein Zielformat überführen. Bisher bietet XMLVM XSLT-Dateien zur Überführung von .NET-Bytecode in Java-Quellcode und von Java-Bytecode in JavaScript und Objective-C-Quellcode. Gerade die letzte Transformation erlaubt eine interessante Anwendung. Bisher wurden Programme für das iPhone immer in Objective-C geschrieben, und nach dem aktuellen Verbot alternativer Sprachen wird das vorerst so bleiben. XMLVM kann jedoch Android-Programme in iPhone-Programme konvertieren. (Es sind jedoch keine beliebigen Android-Programme, sondern sie müssen mit einer besonderen Android Compatibility Library geschrieben werden.) XMLVM transformiert den Java-Bytecode in Objective-C und realisiert die spezielle Android-Bibliothek auf dem iPhone Cocoa-Framework. Das Spiel Xokoban wurde so erfolgreich migriert und in den Apple App Store gestellt (allerdings erst nach 5 Monaten).


Rheinwerk Computing - Zum Seitenanfang

19.1.3 Erstellen von Java-BytecodeZur vorigen Überschrift

Ein Generator kann Java-Bytecode direkt erzeugen. Zwei bekannte APIs sind:

Beispiele finden sich auf den Webseiten. Insbesondere ASM ist beliebt, um Bytecode zur Laufzeit zu transformieren, um etwa bei der aspektorientierten Programmierung Aspekte, also Quellcode, einzuweben. Wir wollen das Erzeugen von Bytecode an dieser Stelle nicht weiter vertiefen und dem Compiler das Generieren von Bytecode überlassen.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular