Hier ist noch ein Link auf die Vortragsfolien.
Posts mit dem Label java werden angezeigt. Alle Posts anzeigen
Posts mit dem Label java werden angezeigt. Alle Posts anzeigen
Freitag, 27. September 2013
Video zum Beanfabrics-Vortrag
Im Juni 2013 hatte ich das Vergnügen einen kleinen Vortrag vor den Mitarbeitern der TNG halten zu dürfen. Das Thema war Beanfabrics, ein Software-Projekt, dass ich seit 1999 voran treibe. Freundlicherweise hat Gerhard Müller mir gestattet, einen Link auf das Video zu veröffentlichen.
Hier ist noch ein Link auf die Vortragsfolien.
Hier ist noch ein Link auf die Vortragsfolien.
Sonntag, 2. Dezember 2012
Runtime Generic Type Resolution for Java
Bekanntlich stehen in Java die meisten generische Typinformationen zur Laufzeit nicht zur Verfügung. Insbesondere können sie nicht mittels Reflection ausgelesen werden. Doch stimmt diese Aussage unter allen Umständen?
Das Verfahren, bei dem die Typinformationen gelöscht werden, heisst Type Erasure. Damit soll das sogenannte "Generic Legacy Problem" gelöst werden.
Aus Gründen der Rückwärtskompatibilität mit altem Code, hat man sich damals entschieden, beim Kompiliervorgang die Generics weitgehend zu löschen oder in andere Konstrukte zu transformieren. Im Detail löscht der Kompiler die Typparameter, ersetzt die Typrariablen durch ihren Grenztyp, fügt Typecasts hinzu, und erweitert Klassen um sogenannte Brückenmethoden, damit das Overriding auch weiterhin funktioniert.
Folglich kann zur Laufzeit nicht herausgefunden werden, welchen Typ die unten stehende Liste "myList" hat:
Für das obige Beispiel ist das tatsächlich unmöglich. Allerdings ist das nicht die ganze Wahrheit. Wenn wir den Code etwas abwandeln, dann lässt sich die Typinformation auslesen:
Die Klasse "GenericType" kapselt die Suche nach dem Wert des Typparameters, welche mit Hilfe der etwas umständlichen Reflection API realisiert ist. Sie ist Teil des gleichnamigen Github-Projekt GenericType.
Das Verfahren, bei dem die Typinformationen gelöscht werden, heisst Type Erasure. Damit soll das sogenannte "Generic Legacy Problem" gelöst werden.
Aus Gründen der Rückwärtskompatibilität mit altem Code, hat man sich damals entschieden, beim Kompiliervorgang die Generics weitgehend zu löschen oder in andere Konstrukte zu transformieren. Im Detail löscht der Kompiler die Typparameter, ersetzt die Typrariablen durch ihren Grenztyp, fügt Typecasts hinzu, und erweitert Klassen um sogenannte Brückenmethoden, damit das Overriding auch weiterhin funktioniert.
Folglich kann zur Laufzeit nicht herausgefunden werden, welchen Typ die unten stehende Liste "myList" hat:
public class Scribble {
public static void main(String[] args) {
List<String> myList = new ArrayList<String>();
Class elementType = getElementTypeOf(myList);
System.out.println(elementType);
}
/**
* Returns the generic element type of the given list.
*/
public static Class getElementTypeOf(List list) {
// This is not possible in Java !?!
}
}
Für das obige Beispiel ist das tatsächlich unmöglich. Allerdings ist das nicht die ganze Wahrheit. Wenn wir den Code etwas abwandeln, dann lässt sich die Typinformation auslesen:
public class Scribble {
static List<String> myList = new ArrayList<String>();
public static void main(String[] args) {
Class elementType = getElementTypeOf("myList");
System.out.println(elementType);
}
/**
* Returns the generic element type of the field with the given name
*/
public static Class getElementTypeOf(String fieldname) {
return new GenericType(Scribble.class)
.getFieldType( fieldname)
.getTypeParameter(List.class.getTypeParameters()[0])
.asClass();
}
}
Das liegt daran, dass die Variable "myList" nun als Member der Klasse deklariert ist, d.h. mit vollständigen Metainformationen im Classfile abgelegt wird.
Die Klasse "GenericType" kapselt die Suche nach dem Wert des Typparameters, welche mit Hilfe der etwas umständlichen Reflection API realisiert ist. Sie ist Teil des gleichnamigen Github-Projekt GenericType.
Dienstag, 1. November 2011
Ein Codegenerator für POJO-Builders
Seit Ende September beschäftige ich mich auf Github mit einem Open-Source-Projekt mit dem Namen PojoBuilder.
Ich entwickle dort einen Java-Codegenerator, der für ein beliebiges POJO einen passenden Pojo-Builder erzeugen kann. Dieser bietet die folgende Funktionalitäten:
- ein "fluent interface" zum Festlegen der Eigenschaftswerte des POJOs
- und eine build-Methode für die Erstellung einer neuen POJO Instanz mit diesen Werten
Ein kleines Beispiel
Der Codegenerator lässt sich naheliegenderweise am einfachsten anhand eines Code-Beispiels erläutern. Nehmen wir an, wir haben die folgende Javaklasse erstellt und benötigen dafür einen Builder.public class Contact {
private final String name;
private String email;
public Contact(String aName) {
this.name = aName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
}
Der dazugehörige Builder sollte beispielsweise auf die folgende Weise bedient werden können:
Contact james = new ContactBuilder()
.withName("James Bond")
.withEmail("007@secretservice.org")
.build();
Mit dem Einsatz von PojoBuilder lässt sich dieses Ziel durch die Markierung der Klasse mit zwei Annotations erreichen.
@GeneratePojoBuilder(intoPackage = "samples.builder") public class Contact { private final String name; private String email; @ConstructorProperties({ "name"}) public Contact(String aName) { this.name = aName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getName() { return name; } }
Mit @GeneratePojoBuilder wird der Codegenerator angewiesen, für die annotierte Klasse einen Builder zu generieren. Das Attribut intoPackage ist optional und bestimmt das Java-Package, in das der Builder generiert werden soll. Mit @ConstructorProperties legt man fest, welcher Konstruktor verwendet werden soll und wie die Parameter auf Properties abgebildet werden.
Der generierte Builder sieht dann so aus (Kommentare habe ich entfernt):
public class ContactBuilder implements Cloneable {
protected String value$email$java$lang$String;
protected boolean isSet$email$java$lang$String = false;
protected String value$name$java$lang$String;
protected boolean isSet$name$java$lang$String = false;
public ContactBuilder() {
}
public ContactBuilder withEmail( String value) {
this.value$email$java$lang$String = value;
this.isSet$email$java$lang$String = true;
return this;
}
public ContactBuilder withName( String value) {
this.value$name$java$lang$String = value;
this.isSet$name$java$lang$String = true;
return this;
}
public Object clone() {
try {
ContactBuilder result = (ContactBuilder)super.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new InternalError(e.getMessage());
}
}
public ContactBuilder but() {
return (ContactBuilder)clone();
}
public Contact build() {
Contact result = new Contact( this.value$name$java$lang$String );
if ( this.isSet$email$java$lang$String) {
result.setEmail( this.value$email$java$lang$String);
}
return result;
}
}
Wie ist das ganze nun implementiert?
Für die eigentliche Codegenerierung nutze ich, wie schon in früheren Projekten, die Stringtemplate Engine. Offen war ursprünglich lediglich die Frage, wie der Generator angestoßen werden sollte. Ich wollte erreichen, dass er sowohl in einem Build-Lauf als auch in einer IDE genutzt werden kann. Da sie sich für beides sehr gut eignet, habe ich mich für die Annotation Processor API entschieden, die seit Java 5 verfügbar ist. Somit war auch festgelegt, dass der Generator über Annotations gesteuert wird (und nicht etwa über XML oder eine andere Beschreibungssprache).Automatisierte Tests
Eine Schwierigkeit im Projekt ist allerdings die Frage, wie man einen Annotation-Processor ordentlich testet, und zwar im Rahmen von Acceptance-Tests sowie Unit-Tests (ja, ich gebe zu, die Tests nicht vorher geschrieben zu haben). Bislang meine ich immerhin einen guten Weg für die Acceptance-Tests gefunden zu haben, und zwar mittels einer JavaProject-Fassade für die Java Compiler API, mit der ich tatsächlich on-the-fly ein kleines Java-Projekt komplett mit Sourcen erstellen und kompilieren kann. Anschließend kann ich in meinem TestCase die generierten Klassen per Classloader laden und mittels Reflection prüfen, oder alternativ dazu, könnte ich auch vorbereitete Unit-Tests laufen lassen, welche die generierten Builder testen.Was allerdings das Unit-Testen des AnnotationProcessor angeht, tappe ich noch im Dunkeln. Grundsätzlich bin ich ein ein Fan von Interaction-Based-Testing, allerdings erscheint mir in diesem Fall mit Mock-Objekten zu arbeiten sehr aufwändig. Alternativ dazu bleibt wohl nur ein State-Based Ansatz, wie etwa in diesem Post auf Stackoverflow beschrieben.
Abonnieren
Posts (Atom)
