08 Xtend - HTW Dresden

Transcrição

08 Xtend - HTW Dresden
Software Factories
8 Xtend
WS 2016/17
Prof. Dr. Dirk Müller
Übersicht
●
Motivation: pro und contra Java
●
Hello-World-Beispiel
●
Sichtweisen, Einordnung und Geschichte
●
Beispiel „Liste von Strings in Großbuchstaben“
●
wichtige Schlüsselwörter
●
Active Annotations
●
Operator-Überladung
●
Template Expressions
●
Codegenerierung für das HelloDSL-Beispiel
●
Codegenerierung für einen Prolog-zu-Scheme-Transpiler
●
Zusammenfassung
Dirk Müller: Software Factories
WS 2016/17
2/37
Java
●
Vorteile
–
–
–
–
●
plattformunabhängig durch Bytecode-Ansatz, Ausführung auf JVM
objektorientierte und imperative Paradigmen kombiniert
mächtige Bibliotheken
sehr gute integrierte Entwicklungsumgebungen (frei) verfügbar
Nachteile
Umwandlung einer Liste von
Strings in die zugehörige Liste
von Strings in Großbuchstaben
– Redundanz im Quellcode mit Typangaben
– funktionales Paradigma kaum umgesetzt
– keine Operatorüberladung
public ArrayList<String> toUpperCase(final List<String> strings) {
final ArrayList<String> result = new ArrayList<String>();
for (final String string : strings) {
String _upperCase = string.toUpperCase();
result.add(_upperCase);
}
return result;
}
Dirk Müller: Software Factories
Quelle:
[2]
WS 2016/17
3/37
Hello World!
package example1
class HelloWorld {
def static void main(String[] args) {
println('Hello World!')
package example1;
}
import org.eclipse.xtext.xbase.lib.InputOutput;
}
@SuppressWarnings("all")
public class HelloWorld {
public static void main(final String[] args) {
InputOutput.<String>println("Hello World!");
}
}
●
●
Xtend-Code wird beim Speichern automatisch (on-thefly) in korrespondierenden Java-Code übersetzt und im
Ordner xtend-gen (anpassbar) abgelegt
auffällige Einsparungen
– Semikolons als Trenner zwischen Anweisungen
– Rückgabetypen
Dirk Müller: Software Factories
WS 2016/17
4/37
Java 10 bereits heute
●
flexibler und ausdrucksstarker
Java-Dialekt
– keine Interoperabilitätsprobleme mit Java
●
●
wird zu gut lesbarem Java-5-kompatiblen
(anpassbar) Code kompiliert
nahtlose Nutzung bestehender
Java-Bibliotheken
●
Ausführung (fast) so schnell wie bei Handcodierung
●
Paradigmen: objektorientiert, imperativ und funktional
●
●
Typ-Inferenz, Makros (Active Annotations), LambdaAusdrücke und Operatorüberladung als einige Highlights
statische Typisierung
– gut für Fehlersuche und IDE-Integration
Dirk Müller: Software Factories
Quelle: [5]
WS 2016/17
5/37
Android-GUI-Programmierung mit Xtend
●
nur von Mini-Laufzeitbibliothek org.eclipse.xtend.lib
abhängig, die hauptsächlich aus Google Guava besteht
– Google Guava sollte sowieso genutzt werden
WS 2016/17
Quelle: [7]
Dirk Müller: Software Factories
6/37
Sichtweisen auf Xtend
●
●
●
abgespecktes Java
ideale Ergänzung zu einer mit Xtext spezifizierten DSL, um
einen Übersetzer (Transpiler) oder einen Compiler selbst
zu schreiben
„Xtend ist ein Java-Dialekt, der mir weniger Geschwätz
und mehr Funktionalität bietet“ [4]
●
„Java + Xtend == Better Java“ [2]
●
ein integrierender Java-Transpiler
●
ein typsicherer Ersatz für Java Server Pages (JSP) [6]
Dirk Müller: Software Factories
WS 2016/17
7/37
Einordnung
●
JVM-Sprachen nutzen die hochoptimierte Java Virtual
Machine zur Ausführung von Bytecode
org.eclipse.xtend.lib
●
z. B. Scala, Groovy, Clojure, JRuby
Scala-Quellcode
(.scala)
Java-Quellcode
(.java)
Groovy-Quellcode
(.groovy)
javac
Clojure-Quellcode
(.clj,cljs,.cljc,.edn)
Quelle: [6]
WS 2016/17
Xtend-Quellcode
(.xtend)
Vorteile
● Performanz
● einfache Integration
● einfache Fehlerbehebung via Java-Debugger
Java-Bytecode (.class)
JRuby-Quellcode
(.rb)
On-the-fly-Transpiler
JVM
JVM
JVM
Windows
Linux
Mac
Dirk Müller: Software Factories
8/37
Geschichte
●
Ursprünge in openArchitectureWare (2006-2011)
– Xpand als statisch typisierte Templatesprache für
JAX Innovation
Award 2007:
1. Groovy
3. oAW
M2C-Transformationen
– Xtend als funktionale Sprache, um das Metamodell mit
zusätzlicher Logik erweitern zu können (M2M-Transformationen)
●
●
2011 Xtend2 (Suffix .xtend) als Ersatz für Xpand (.xpt)
und Xtend (.exp), später Namensvereinfachung zu Xtend
Version 2.7.0 vom 2.9.2014
– reife Makros via Active Annotations, z. B. @Accessors
●
Version 2.8.0 vom 11.3.2015
– Reverse Engineering von Java zu Xtend incl. Warnungen bei
unübersetzbarem Code (FixMe-Tags) unterstützt
●
Version 2.9.0 vom 1.12.2015
– Unterstützung von IntelliJ IDEA und Android Studio
●
aktuelle Version 2.9.2 vom 10.03.2016
WS 2016/17
Quellen: [5][6]
Dirk Müller: Software Factories
9/37
Beispiel Liste von Strings in Großbuchstaben
public ArrayList<String> toUpperCase(final List<String> strings) {
final ArrayList<String> result = new ArrayList<String>();
for (final String string : strings) {
String _upperCase = string.toUpperCase();
result.add(_upperCase);
}
return result;
}
Java
Xtend imperativ
def toUpperCase(List<String> strings) {
val result = new ArrayList<String>()
for (string : strings) {
result += string.toUpperCase
}
return result
}
Xtend funktional
10 Auftreten von
„String“/„string“
sehr ausdrucksstark
3 Auftreten von
„String“/„string“
def toUpperCase(List<String> strings) {
strings.map( string | string.toUpperCase )
}
def toUpperCase(List<String> strings) {
Xtend funktional
strings.map[toUpperCase]
mit Lambda-Ausdrücken
Quelle: [2]
WS 2016/17
}
Dirk Müller: Software Factories
10/37
Wichtige Schlüsselwörter
●
class zur Deklaration einer Klasse
– Standardsichtbarkeit ist public
– package zu package private übersetzt (Standard in Java)
– mehrere Public-Klassen in einer Datei erlaubt
– Konstruktoren mit new statt des Klassennamens, public als Std.
●
Deklaration von Klassenvariablen
– Standardsichtbarkeit ist private
– val zur Deklaration einer Wert-Variable (final)
– var zur Deklaration einer (echten) Variable, Weglassung möglich
– Weglassung des Typs erlaubt, falls Ableitung aus Initialisierung
●
def zur Deklaration einer Methode
– Standardsichtbarkeit ist public
– Rückgabetyp kann aus dem Methodenrumpf abgeleitet werden,
außer bei abstrakten und rekursiven Methoden
Quelle: [5]
WS 2016/17
Dirk Müller: Software Factories
11/37
Makros via Active Annotations
●
schematische wiederkehrender Code (Boilerplate-Code)
– Assistenten (Wizards) in IDEs
Klicken
– Sammlung von Code-Generatoren
unübersichtlich
– wie individueller Code auch handgeschrieben
aufwendig
●
Active Annotations als ein modernes Mittel zur
Codegenerierung
– einfache Integration in existierende Projekte
– kürzere Durchlaufzeiten z. B. zur Erstellung von Prototypen
●
●
●
Grundidee: Übersetzung von Xtend-Code in Java-Code
unter Nutzung einer Bibliothek
können nicht im selben Projekt genutzt werden, nur in
einem vorgeschalteten oder kompiliert in einer JAR-Datei
Schnittstellen im Teil org.eclipse.xtend.lib.macro
der Laufzeitbibliothek definiert
WS 2016/17
Quelle: [5]
Dirk Müller: Software Factories
12/37
Grundlegende Idee hinter MDSD
Code einer ReferenzAnwendung
AnwendungsModell
individueller
Code
generischer
Code
schematisch
wiederkehrender Code
Separierung
Analyse
individueller
Code
schematisch
wiederkehrender Code
Plattform
Dirk Müller: Software Factories
WS 2016/17
13/37
Vorgefertigte Active Annotations
●
org.eclipse.xtend.lib.annotations als Plug-in / JAR
●
Entwurfsmuster in Bibliothek abgelegt
– aktive Weiterentwicklung (z. T. Beta-Versionen)
●
●
@Accessors dient dem Anlegen von Gettern und Settern
für den gekapselten Zugriff auf eine Klassenvariable
@Data dient der Konvertierung einer Klasse in eine
Wertobjekt-Klasse (z. B. Geldbetrag, Datum)
keine Identität, nicht veränderbar, Erzeugung in gültigen Zustand
alles final
Getter-Methoden werden generiert, Setter-Methoden nicht
Konstruktor mit Parametern für alle nicht-initialisierten Felder
equals(Object)- / hashCode()-Methoden werden generiert
– toString()-Methode wird generiert
–
–
–
–
–
●
@Delegate dient der automatischen Implementierung von
Delegierungsmethoden für Schnittstellen
WS 2016/17
Quelle: [5]
Dirk Müller: Software Factories
14/37
Generierung von
Gettern und Settern
package example1
import
org.eclipse.xtend.lib.annotations.Accessors
@Accessors class Person {
String name
String firstName
@Accessors(PUBLIC_GETTER,
PROTECTED_SETTER) int age
@Accessors(NONE) String internalField
}
klassenspezifische
Angaben werden
überschrieben
WS 2016/17
Quelle: [5]
package example1;
import org.eclipse.xtend.lib.annotations.AccessorType;
import org.eclipse.xtend.lib.annotations.Accessors;
import org.eclipse.xtext.xbase.lib.Pure;
@Accessors
@SuppressWarnings("all")
public class Person {
private String name;
private String firstName;
@Accessors({ AccessorType.PUBLIC_GETTER,
AccessorType.PROTECTED_SETTER })
private int age;
@Accessors(AccessorType.NONE)
private String internalField;
@Pure
keine Seiteneffekte
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
@Pure
public String getFirstName() {
return this.firstName;
}
public void setFirstName(final String firstName) {
this.firstName = firstName;
}
@Pure
public int getAge() {
return this.age;
}
protected void setAge(final int age) {
this.age = age;
}}
Dirk Müller: Software Factories
15/37
Operator-Überladung
●
●
Ziel: besser lesbarer Code, Idee von C++ übernommen
näher an der fachlichen Domäne (typisch: Mathematik),
aber Ausdrucksstärke nicht verbessert (syntactic sugar)
package example1
import org.eclipse.xtend.lib.annotations.Data
public Complex(final
@Data
final
u.a. Konstruktor generiert
class Complex {
super();
this.re = re;
val double re
Real- und
this.im = im;
val double im
Imaginärteil
}
def +(Complex other) {
new Complex(re + other.re, im + other.im)
}
Addition
def -() {
entgegengesetzte
new Complex(-re, -im)
Zahl (unärer Operator)
}
def -(Complex other) {
Subtraktion als Addition
this + -other
der entgegengesetzten
}
Zahl
}
double re,
double im) {
Dirk Müller: Software Factories
WS 2016/17
16/37
Test der Operatoren für komplexe Zahlen
class ComplexExample {
def static void main(String[] args) {
val x = new Complex(1, 2)
val y = new Complex(-3, 5)
val z = new Complex(0, 3)
alle Typen var result = -x + y
+= z
abgeleitet result
println(result.toString())
}
}
automatisch
mit verfügbar
wegen @Data mit
generiert, hier über
Xbase-I/O-Bibliothek
aber auch als
println(result)
möglich
package example1;
import example1.Complex;
import org.eclipse.xtext.xbase.lib.InputOutput;
@SuppressWarnings("all")
public class ComplexExample {
public static void main(final String[] args)
{
final Complex x = new Complex(1, 2);
final Complex y = new Complex((-3), 5);
final Complex z = new Complex(0, 3);
Complex _minus = x.operator_minus();
Complex result = _minus.operator_plus(y);
Complex _result = result;
result = _result.operator_plus(z);
String _string = result.toString();
InputOutput.<String>println(_string);
}
}
Dirk Müller: Software Factories
WS 2016/17
17/37
Template Expressions
●
Ziel: gut lesbare String-Verkettung
– wichtig für Compiler/Transpiler
●
●
von Paar dreifacher Anführungszeichen (''') abgegrenzt
interpolierte Ausdrücke von französischen
Anführungszeichen («Guillemets») umschlossen
–
–
–
–
Zeichensatz sollte UTF-8 sein
weniger Probleme mit Escape-Konflikten
über Syntax-Vervollständigung angeboten
explizit (unter Windows) mit CTRL+< bzw. CTRL+>
●
können überall stehen (alles ist ein Ausdruck)
●
Typ ist CharSequence, wird ggf. in String umgewandelt
Dirk Müller: Software Factories
WS 2016/17
18/37
FOR-Schleifen in Templates
– BEFORE fügt Vorspann-Text ein
– AFTER fügt Abspann-Text ein
– SEPARATOR fügt Text zwischen zwei aufeinanderfolgenden
Iterationen ein (Trenner)
– BEFORE und AFTER nur ausgeführt, wenn min. 1 Iteration
– SEPARATOR nur ausgeführt, wenn min. 2 Iterationen
Dirk Müller: Software Factories
WS 2016/17
19/37
Whitespace-Behandlung in Xtend-Templates
<html>
<body>
Traffic light colors:
<ul>
<li>red</li>
<li>yellow</li>
<li>green</li>
</ul>
</body>
</html>
println(new HelloWorld().html)
●
●
●
Formatierung bei Templatesprachen klassisch über
Nachschaltung eines Formatter bzw. Pretty Printer gelöst
Code des Generators und des gen. Codes übersichtlich
Xtend-Ansatz: Unterscheidung, welche Einrückungen für
Ausgabe gedacht sind und welche nicht (Kontrollstrukturen
wie z. B. FOR oder IF), sichtbar per Syntax-Hervorhebung
Dirk Müller: Software Factories
WS 2016/17
20/37
Einhaken in den Generator-Mechanismus
Datei anlegen und
ersten Text dort
eintragen
liefert einen BaumIterator <Eobject>
als Sicht auf diese
Ressource
funktional: filtern,
Namen extrahieren,
mit Kommas trennen
Dirk Müller: Software Factories
WS 2016/17
21/37
Codegenerierung in Java übersetzt
Quelldatei in der
Sprache Xtend
generierte Zieldatei
in Java
Dirk Müller: Software Factories
WS 2016/17
22/37
Test der Codegenerierung
Test in zweiter
Eclipse-Instanz
Speichern als
Trigger für die
Codegenerierung
Dirk Müller: Software Factories
WS 2016/17
23/37
Zielcode generiert
Zieldatei
Dirk Müller: Software Factories
WS 2016/17
24/37
Neuer Eintrag im Quellcode
Dirk Müller: Software Factories
WS 2016/17
25/37
Konsistente Änderung im Zielcode
Dirk Müller: Software Factories
WS 2016/17
26/37
Liste zu grüßender Personen mit Templates
override void doGenerate(Resource resource,
IFileSystemAccess2 fsa, IGeneratorContext context)
{
fsa.generateFile('greetings.txt', 'People to greet: ' +
resource.allContents
.filter(typeof(Greeting))
.map[name]
.join(', '))
ohne Templates
}
override void doGenerate(Resource resource,
mit Templates
IFileSystemAccess2 fsa, IGeneratorContext context)
{
fsa.generateFile('greetings.txt', 'People to greet: ' + '''
«FOR greeting : resource.allContents
.filter(Greeting)
.toIterable
SEPARATOR ', '»«greeting.name»«ENDFOR»
''')
wichtig, dass in 1 Zeile, sonst Umbrüche in der Liste
}
Dirk Müller: Software Factories
WS 2016/17
27/37
Einstellungen für den Xtend-Transpiler
Java-Version für Zielcode
Warnungen unterdrückt
Zielverzeichnis
Nachfrage beim Versuch,
generierte Dateien zu editieren
Dirk Müller: Software Factories
WS 2016/17
28/37
Beispiel: Lied „99 Bottles of Beer“
99 Bottles of Beer
99 bottles of beer on the wall, 99 bottles of beer.
Take one down and pass it around, 98 bottles of beer on the wall.
98 bottles of beer on the wall, 98 bottles of beer.
Take one down and pass it around, 97 bottles of beer on the wall.
[..]
2 bottles of beer on the wall, 2 bottles of beer.
Take one down and pass it around, 1 bottle of beer on the wall.
„One“ als Variante
„one“
„one“ als Variante
1 bottle of beer on the wall, 1 bottle of beer.
Take one down and pass it around, no more bottles of beer on the wall.
No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.
WS 2016/17
Quelle: [8]
Dirk Müller: Software Factories
29/37
Bottles-Song in BASIC
10
20
30
40
50
60
REM BASIC Version of 99 Bottles of beer
FOR X=100 TO 1 STEP -1
PRINT X;"Bottle(s) of beer on the wall,";X;"bottle(s) of beer"
PRINT "Take one down and pass it around,"
Singular/Plural unsauber
PRINT X-1;"bottle(s) of beer on the wall"
NEXT
Endvers nicht abgebildet
10 REM Basic version of 99 bottles of beer
20 REM Modified by M. Eric Carr ([email protected])
30 REM from prior version found on this site.
40 REM (Modified to correct "1 bottle" grammar)
50 FOR X=99 TO 1 STEP -1
60 PRINT X;"bottle";
GOSUB-Unterprogramm
nicht genutzt
70 IF X<>1 THEN PRINT "s";
80 PRINT " of beer on the wall,";X;"bottle";
90 IF X<>1 THEN PRINT "s";
100 PRINT " of beer"
110 PRINT "Take one down and pass it around,"
120 PRINT X-1;"bottle";
130 IF X<>1 THEN PRINT "s";
140 PRINT " of beer on the wall"
Endvers nicht abgebildet
150 NEXT
WS 2016/17
Quelle: [8]
Dirk Müller: Software Factories
30/37
Bottles-Song in Java
class bottle
{
public static void main(String args[])
{
String s = "s";
for (int beers=99; beers>-1;)
{
System.out.print(beers + " bottle" + s + " of beer on the wall, ");
System.out.println(beers + " bottle" + s + " of beer, ");
if (beers==0)
{
System.out.print("Go to the store, buy some more, ");
System.out.println("99 bottles of beer on the wall.\n");
System.exit(0);
}
else
„0 bottles“ statt
System.out.print("Take one down, pass it around, ");
„no more bottles“
s = (--beers == 1)?"":"s";
System.out.println(beers + " bottle" + s + " of beer on the wall.\n");
}
}
ternärer Operator
}
WS 2016/17
Quelle: [8]
Dirk Müller: Software Factories
31/37
Bottles-Song in Xtend
class BottlesSong {
def static void main(String[] args) {
println(new BottlesSong().singTheSong(99))
}
Template Expression
def singTheSong(int all) '''
«FOR i : all .. 1»
«i.Bottles» of beer on the wall, «i.bottles» of beer.
Take one down and pass it around, «(i - 1).bottles» of beer on the wall.
«ENDFOR»
No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, «all.bottles» of beer on the wall.
'''
def static bottles(int i) {
switch i {
case 0 : 'no more bottles'
1 als
case 1 : 'one bottle'
Zahlwort
default : '''«i» bottles'''
}.toString
}
def static Bottles(int i) {
bottles(i).toFirstUpper
}
}
Powerful Switch Expression
Template Expression
Extension method
Everything is an expression.
Dirk Müller: Software Factories
WS 2016/17
32/37
Codegenerierung für einen
Prolog-zu-Scheme-Transpiler
/*
* generated by Xtext 2.9.2
*/
package de.htwdd.sf.beleg.muellerd.generator
import
import
import
import
Xtext generiert eine Schablone
org.eclipse.emf.ecore.resource.Resource
org.eclipse.xtext.generator.AbstractGenerator
org.eclipse.xtext.generator.IFileSystemAccess2
org.eclipse.xtext.generator.IGeneratorContext
/**
* Generates code from your model files on save.
*
* See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation
*/
class PrologDslGenerator extends AbstractGenerator { Callback-Routine, die beim Speichern
}
override void doGenerate(
Resource resource,
IFileSystemAccess2 fsa,
IGeneratorContext context)
{
}
muss noch gefüllt
des Quelltextes aufgerufen wird
AST vom Parsen mittels Xtext geliefert
Zugriff aufs Dateisystem
werden
Dirk Müller: Software Factories
WS 2016/17
33/37
Implementierung von doGenerate
class PrologDslGenerator extends AbstractGenerator {
String code
// zur Konstruktion des auszugebenden Textes
override void doGenerate(Resource resource,
IFileSystemAccess2 fsa,
Filtern des passenden
IGeneratorContext context)
Typs und iterierbar
{
machen
notwendige Initialisierung zum leeren String
code = ""
for (e : resource.allContents.filter(PrologDsl).toIterable)
{
Kontrollausgabe
System.out.println("Start Transformieren")
in der Konsole der
e.traversiere
Methode, die noch für alle
ersten Eclipse-Instanz
Typen implementiert wird
}
fsa.generateFile("PrologDsl.lsp", code)
Herausschreiben des
}
Endergebnisses
def conc(String str) {
code = code + str;
}
}
// zum Anfügen neuer Programmteile
Hilfsmethode
Dirk Müller: Software Factories
WS 2016/17
34/37
Implementierung einer Regel in Xtend
<prologdsl> → ( prolog (quote <program>) (quote <query> ))
def traversiere(PrologDsl k) {
conc("(prolog (quote")
noch zu implementieren
k.program.traversiere
Zeilenumbruch zur besseren Lesbarkeit
conc(")\r\n(quote")
k.query.traversiere
noch zu implementieren
conc("))")
}
Besser lesbare Lösung mittels Templates möglich?
Dirk Müller: Software Factories
WS 2016/17
35/37
Zusammenfassung
●
Xtend als statisch typisierte, auf Java aufbauende Sprache
– Typen können oft abgeleitet werden
– funktionales Paradigma neben dem objektorientierten und
imperativen unterstützt
– alles ist ein Ausdruck => flexible Komponierbarkeit
– sehr kompakter und ausdrucksstarker Code
●
gute Eignung zur Code-Generierung
– Implementierung der Methode doGenerate
●
gute Template-Unterstützung
– flexible Kontrollstrukturen
– besonders gut gelungen: Behandlung von Whitespaces mit guter
Lesbarkeit im Code-Generator und im generierten Code
●
Makros für schematisch wiederkehrenden Code via Active
Annotations und Operatorüberladung als Highlights
Dirk Müller: Software Factories
WS 2016/17
36/37
Literatur
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
Hartmut Fritzsche, „Software Factories – Skript zur Lehrveranstaltung“, 11.01.2016,
Download am 6.4.2016,
http://www2.htw-dresden.de/~fritzsch/SF/Software_Factories_Skript.pdf
Sven Efftinge, Sebastian Zarnekow: „Extending Java“, in: Pragmatic Programmer
Magazine, Dezember 2011, Download am 30.04.2016,
https://pragprog.com/magazines/2011-12/extending-java
Alex Blewitt: „Xtend Extends Java“, in: InfoQ, Juni 2012; (Interview mit Sven Efftinge),
Download am 30.04.2016, http://www.infoq.com/news/2012/06/xtend-release-10
Jan Koehnlein auf Twitter, 21.04.2016, Download am 30.04.2016,
https://twitter.com/JAXenter/status/723098924114833408
Xtend-Dokumentation, https://eclipse.org/xtend/index.html
Meinte Boersma: „Meinte's DSL-Blog: Using Xtext’s Xtend(2) language“, 19.09.2011,
Download am 3.5.2016,
https://dslmeinte.wordpress.com/2011/09/19/using-xtexts-xtend2-language/
Sven Efftinge: „sven eftinge's blog: Writing Android UIs with Xtend“, 12.12.2011,
Download am 6.5.2016,
http://blog.efftinge.de/2011/12/writing-android-uis-with-xtend.html
Oliver Schade: „99 Bottles of Beer“, 2016, http://99-bottles-of-beer.net
Dirk Müller: Software Factories
WS 2016/17
37/37

Documentos relacionados