Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Neues in Java 7
2 Threads und nebenläufige Programmierung
3 Datenstrukturen und Algorithmen
4 Raum und Zeit
5 Dateien, Verzeichnisse und Dateizugriffe
6 Datenströme
7 Die eXtensible Markup Language (XML)
8 Dateiformate
9 Grafische Oberflächen mit Swing
10 Grafikprogrammierung
11 Netzwerkprogrammierung
12 Verteilte Programmierung mit RMI
13 RESTful und SOAP Web-Services
14 JavaServer Pages und Servlets
15 Applets
16 Datenbankmanagement mit JDBC
17 Technologien für die Infrastruktur
18 Reflection und Annotationen
19 Dynamische Übersetzung und Skriptsprachen
20 Logging und Monitoring
21 Java Native Interface (JNI)
22 Sicherheitskonzepte
23 Dienstprogramme für die Java-Umgebung
Stichwort
Download: - openbook, ca. 21,3 MB
Buch bestellen
Ihre Meinung?
<< zurück | Galileo Computing / <openbook> / Java 7 - Mehr als eine Insel | vor >>
Java 7 - Mehr als eine Insel von Christian Ullenboom Das Handbuch zu den Java SE-Bibliotheken
Java 7 - Mehr als eine Insel Galileo Computing 1433 S., 2012, geb. 49,90 Euro, ISBN 978-3-8362-1507-7 |
| 19 Dynamische Übersetzung und Skriptsprachen
|  | 19.1 Codegenerierung
|  |  | 19.1.1 Generierung von Java-Quellcode
|  |  | 19.1.2 Codetransformationen
|  |  | 19.1.3 Erstellen von Java-Bytecode
|  | 19.2 Programme mit der Compiler API übersetzen
|  |  | 19.2.1 Java Compiler API
|  |  | 19.2.2 Fehler-Diagnose
|  |  | 19.2.3 Eine im String angegebene Compiliationseinheit übersetzen
|  |  | 19.2.4 Wenn Quelle und Ziel der Speicher sind
|  | 19.3 Ausführen von Skripten
|  |  | 19.3.1 JavaScript-Programme ausführen
|  |  | 19.3.2 Groovy
|  | 19.4 Zum Weiterlesen
# 19 Dynamische Übersetzung und Skriptsprachen »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: Ein Generator überführt die Darstellungsform in Java-Quellcode, der später wie die anderen Java-Klassen eines Projekts übersetzt wird. In einer erweiterten Form wird nicht nur Java-Quellcode erzeugt, sondern mit einem nachgeschalteten Compiler der Programmcode gleich übersetzt, sodass am Ende Klassendateien stehen oder sogar ein Java-Archiv. Die generierten Java-Klassen können vor dem Entwickler verborgen werden, indem sie zwar temporär generiert, aber nach dem Übersetzungsvorgang sofort wieder gelöscht werden. Der Nachteil des Generator-Ansatzes ist, dass im Build-Prozess ein extra Schritt nötig ist. Da der Entwickler aber immer sieht, was der Generator macht, ist dieser Prozess gut zu verstehen. Die Beschreibung wird zur Laufzeit über einen Interpreter (oder Generator) verarbeitet. Es entstehen möglicherweise auch neue Klassendateien (vielleicht sogar mit dem Java-Standard-Compiler), aber das Generieren findet zur Laufzeit statt und nicht wie im ersten Fall vor dem Start des Programms. Es ist ein wenig mit der JVM vergleichbar, die Java-Programme in Maschinencode übersetzt. Traditionelle Compiler machen das vor dem Programmstart, und eine moderne JVM übersetzt dynamisch zur Laufzeitzeit. 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. ## 19.1 Codegenerierung 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. ### 19.1.1 Generierung von Java-Quellcode 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: Apache Velocity (http://velocity.apache.org/) FreeMarker (http://freemarker.sourceforge.net/) StringTemplate (http://www.stringtemplate.org/) #### 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: Eclipse Abstract Syntax Tree (AST) aus dem JDT. Eclipse bietet eine API, mit der sich Java-Programmcode aufbauen lässt. CodeModel (https://codemodel.dev.java.net/). Ein »Abfallprodukt« von JAXB, da JAXB mit xjc aus einem XML-Schema den Quellcode von JavaBeans generiert. 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. ### 19.1.2 Codetransformationen 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).
### 19.1.3 Erstellen von Java-Bytecode Ein Generator kann Java-Bytecode direkt erzeugen. Zwei bekannte APIs sind: ASM (http://asm.ow2.org/) BCEL (http://jakarta.apache.org/bcel/) 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
<< zurück | <top> | vor >>
Zum Katalog
Java 7 – Mehr als eine Insel Jetzt bestellen
Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen? Ihre Meinung
Buchempfehlungen
| Java und XML
| Einstieg in Eclipse 3.7
| Android 3
| NetBeans Platform 7
| Java ist auch eine Insel
| Apps entwickeln für Android 4
| Java 7
Shopping
Versandkostenfrei bestellen in Deutschland und Österreich Info

Copyright © Galileo Press 2012
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

[Galileo Computing]
Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de