BlogKontaktTagcloud

Generische Decorator in Java mit Reflection

In unserer Eclipse CDT Semesterarbeit geht es zügig voran. Emanuel schreibt eine Erweiterung für den Abstract Syntax Tree um Kommentare zu speichern. Kommentare sind im AST meist nicht nötig, für Refactorings ist es aber entscheiden das Kommentare nicht einfach auf der Strecke bleiben oder gar verloren gehen.

Um die Kommentare zu Speichern hat Emanuel den Knoten mit der Kommentarinformationen „dekoriert“ und dazu das Decoratorpattern verwendet. Dazu hat er das Interface des entsprechende Knotens implementiert und für die Funktionen des ursprünglichen Knotens einfach einen Wrapper geschrieben der auf die Methoden des „echten“ Knoten zugreift. Dies ist das übliche Vorgehen bei einer solchen Problemstellung. Das kann bei vielen Knoten schnell in einem grossen Programmieraufwand enden. Da wir uns entschieden die Kommentare nur in zwei Knoten zu speichern hielt sich der Aufwand dazu in Grenzen. Trotzdem bemerkten wir auch in diesem Fall dass das Ganze auch sehr fehleranfällig ist. In unserem Dekorator hatte sich trotz kleines Umfanges bereits ein Fehler versteckt, den wir zum Glück beim durchsehen des Codes noch fanden.

Der Vorschlag unseres Professors war nun das Problem generischer mit Reflection zu lösen wie das auch in Ruby oder Smalltalk gehen soll. Eine andere Möglichkeit währe es wohl die Klassen statisch mit einem Script automatisch zu erzeugen. Wir haben letzte Woche nun versucht den Lösungsvorschlag unseres Professors zu implementieren. Dazu verwendeten wir die „dynamic proxies“-Funktionalität von Java wie sie seit Java 1.3 vorhanden ist und unter anderem auch für Java RMI verwendet wird.

Zuerst müssen wir nun eine Factory erstellen welche das Proxy-Objekt erstellt, da dies über einen gewöhnlichen Konstruktor nicht mehr möglich ist. Der Factory übergeben wir den Orginal Knoten der dekoriert werden soll. Das sieht in unserem Beispiel dann so aus:

public static Object invocationFactory(CPPASTNode node) {
CPPASTCommentedInvocationHandler handler = new CPPASTCommentedInvocationHandler(
node);

Class[] interfaces = getAllInterfaces(node);

Object o = Proxy.newProxyInstance(IASTCommentDecorator.class
.getClassLoader(), interfaces, handler);

return o;
}

Zuerst wird der Handler erzeugt der die Zugriffe die auf das Proxy-Objekt erfolgen handhabt. Auf die Funktionalität des Handlers gehe ich später in diesem Text ein. Mit der Methode „getAllInterfaces“ werden danach alle Interfaces des Knoten zurückgegeben und mit dem Interface das die Funktionalität zum Speichern von Knoten bereitstellt ergänzt. Danach wird ein Proxy-Objekt („Proxy.newProxyInstance“) für diese Elemente mit diesen Interfaces und dem Handler erstellt.

private static Class[] getAllInterfaces(CPPASTNode node) {
Class[] interfaces = node.getClass().getInterfaces();
Class[] allInterfaces = new Class[interfaces.length + 1];

for (int i = 0; i < interfaces.length; ++i) {
allInterfaces[i] = interfaces[i];
}

allInterfaces[allInterfaces.length - 1] = IASTCommentDecorator.class;
return allInterfaces;
}

Der Handler ist dann ein relativ einfaches Konstrukt welches das InvocationHandler-Interface implementiert. Welches einzig und allein die invoke Methode verlangt.

public class CPPASTCommentedInvocationHandler implements InvocationHandler 

Im Konstruktor wird der Orginalknoten gespeichert und ein Objekt der Klasse die das Speichern der Kommentare vornimmt angelegt. Die Speicherfunktionalität hätte auch im Handler selbst implementiert werden können, eine eigene Klasse löst aber die Kopplung und erhöht die Wiederverwendbarkeit des Codes.

In der invoke Methode landen nun alle Aufrufe an das Proxy-Objekt. Zuerst werden nun hier die Methoden equals und acceppt abgefangen da sie sowohl mit dem Knoten als auch mit dem Kommentar-Speicher interagieren. und deshalb direkt im Handler behandelt werden.

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {

if (method.getName().equals("equals") && args.length == 1
&& args[0].getClass().equals(Object.class)) {

return equalsMethodeCall(args[0]);
} else if (method.getName().equals("accept") && args.length == 1
&& ASTVisitor.class.isInstance(args[0]) ){

return acceptMethodeCall((ASTVisitor) args[0]);
} else {
return standardClassCalls(method, args);
}
}

In der Methode „standardClassCalls“ werden nun die übrigen Methodenaufrufe an die beiden Klassen „verteilt“. Alle Methoden die vom IASTCommentDecorator ausgehen werden an das Objekt commentExt, welches die Speicherfunktionen für die Kommentare (also das Interface IASTCommentDecorator) implementiert. Alle anderen Methoden wurden ja von den Interface des konkreten Knotens implementiert und werden deshalb auch an diesen weitergeleitet.

private Object standardClassCalls(Method method, Object[] args)
throws IllegalAccessException, InvocationTargetException {
if (method.getDeclaringClass().equals(IASTCommentDecorator.class)) {
return method.invoke(commentExt, args);
} else {
return method.invoke(node, args);
}
}

Die Implementation auf diese Weise ist sehr flexibel und elegant, auch wenn sie ein wenig schwer zu verstehen ist. Leider kann das Proxy-Objekt nur in die entsprechende Interface, und nicht in eine Knotenklasse, gecastet werden. Da in Eclipse CDT, wie vermutlich in den meisten grösseren Projekten, nicht konsequent gegen Interfaces programmiert würde funktioniert dieser Lösungsansatz bei uns leider nicht.

Ähnliche Beiträge:
Developing Eclipse CDT
Fertig!
Mein erster Patch
Generisches toString in Java mit Reflection
Named parameters in Java (bgl-style)
Comments (0)  Permalink

comments

add a comment

The Trackback URL to this comment is:
http://leo.freeflux.net/blog/plugin=trackback(1108).xml

Keine (weiteren) neuen Kommentare erlaubt.