Repräsentation von Java-Fakten für den constraintbasierten

Werbung
FernUniversität in Hagen
Fakultät für Mathematik und Informatik
Lehrgebiet Programmiersysteme
Prof. Dr. Friedrich Steimann
Repräsentation von Java-Fakten
für den constraintbasierten Refaktorisierungsansatz
mit Hilfe von TGraphen
Masterarbeit im Studiengang
Master of Science in Informatik
vorgelegt von
Matthias Kleine
[email protected]
betreut durch
Prof. Dr. Friedrich Steimann,
Dipl.-Inform. Andreas Thies
Juli 2011
1
2
Danksagung
Das Erstellen dieser Arbeit stand im Zeichen der Geburt meiner Tochter Alina. Ihre ersten
Lebensmonate werden in meiner Erinnerung für immer mit dieser Arbeit verbunden sein. Es wäre
mir, gerade unter diesen Umständen, nicht möglich gewesen, die Arbeit ohne die Unterstützung und
das Verständnis meiner Frau Simona abzuschließen. Ihr gilt daher mein besonderer Dank.
3
4
Inhaltsverzeichnis
1 Einleitung.......................................................................................................................................11
1.1 Aufbau der Arbeit...................................................................................................................11
2 Der constraintbasierte Refaktorisierungsansatz............................................................................13
2.1 Grundideen und -begriffe.......................................................................................................14
2.1.1 Variablen...........................................................................................................................14
2.1.2 Relationen und Constraint-Regeln....................................................................................14
2.1.3 Constraints und Constrainterfüllung.................................................................................15
2.1.4 Das Finden einer constrainterfüllenden Refaktorisierung................................................15
2.2 Benötigte Java-Fakten für Sichtbarkeitsconstraints...............................................................16
2.2.1 Übersicht der Constraint-Regeln.......................................................................................16
2.2.2 Eigenschaften von Deklarationen und Referenzen...........................................................17
2.2.3 Implizite Quelltext-Elemente............................................................................................19
2.2.4 Öffentliche Eigenschaften externer Bibliotheken.............................................................19
2.2.5 Rückreferenzierung des Quelltexts und Kommentare......................................................20
2.3 Bisherige Ansätze zur Repräsentation der Fakten.................................................................20
2.3.1 Repräsentation als Eclipse-AST.......................................................................................20
2.3.2 Erweiterte Faktenextraktion und Speicherung in Hashtabellen........................................20
2.3.3 Nutzung eines Java-Compilers.........................................................................................21
2.3.4 Weitere Graphen-basierte Ansätze....................................................................................21
2.3.5 Sonstige Alternativen........................................................................................................22
2.3.6 Zusammenfassung der Probleme der bisherigen Ansätze.................................................22
3 TGraphen zur Repräsentation von Java-Fakten.............................................................................23
3.1 Bezeichnung...........................................................................................................................23
3.2 Formale Eigenschaften von TGraphen..................................................................................23
3.3 Beispiel für einen TGraphen..................................................................................................23
3.4 Repräsentation von Software-Systemen mittels TGraphen...................................................24
3.5 Kurze Entwicklungsgeschichte..............................................................................................25
3.6 Architekturübersicht zu GraBaJa und dem Graphenlabor.....................................................26
3.7 Einige Eigenschaften im Detail.............................................................................................27
3.7.1 Faktenextraktion...............................................................................................................27
3.7.2 Metamodell.......................................................................................................................28
3.7.2.1 Einfacher Beispiel-Quelltext..................................................................................28
3.7.2.2 Programmwurzel und Dateibezug..........................................................................29
3.7.2.3 Typ- und Methoden-Definitionen...........................................................................30
3.7.2.4 Subtyp-Beziehungen und Methoden-Aufrufe........................................................31
3.7.2.5 Rückreferenzierung des Quelltexts........................................................................32
3.7.3 Die Abfragesprache GReQL.............................................................................................33
3.7.3.1 Bezeichnung, Komponenten und Geschichte.........................................................33
3.7.3.2 Einblick in die Syntax............................................................................................33
3.7.3.2.1 Ausdrücke.......................................................................................................33
5
3.7.3.2.2 from-with-report-Ausdrücke..........................................................................34
3.7.3.2.3 Pfadbeschreibungen als reguläre Ausdrücke..................................................34
3.7.3.2.4 Beispiel einer Abfrage an den TGraphen........................................................35
3.8 Welche Vorteile können erhofft werden?...............................................................................35
3.8.1 Lösung der Probleme anderer Ansätze.............................................................................35
3.8.2 Einige formale Vorteile.....................................................................................................36
3.8.3 Eignung für programmanalytische Aufgaben...................................................................36
3.8.4 Programmiersprachenübergreifende Refaktorisierungen.................................................36
3.9 Vorgehensweise bei Anpassungen im TGraphen...................................................................37
3.9.1 Anpassung des Metamodells.............................................................................................39
3.9.1.1 Ergänzung von Attributen zu existierenden Typen.................................................39
3.9.1.1.1 Typen für Attribute..........................................................................................39
3.9.1.1.2 Ergänzung der Attributliste eines Typs...........................................................40
3.9.1.2 Ergänzung neuer Knoten- oder Kantentypen.........................................................40
3.9.2 Modifikation des generierten TGraphen...........................................................................41
3.9.2.1 Traversieren und Abfragen.....................................................................................41
3.9.2.1.1 Traversieren aller Knoten des Graphen..........................................................41
3.9.2.1.2 Traversieren aller Kanten des Graphen...........................................................41
3.9.2.1.3 Traversieren aller Inzidenzen zu einem Knoten.............................................42
3.9.2.1.4 Suche nach Knoten bei fixem Pfad.................................................................42
3.9.2.1.5 Suche nach Knoten von einem Knoten aus....................................................42
3.9.2.1.6 Suche nach Knoten auf Graph-Ebene mittels GReQL...................................43
3.9.2.1.7 Suche nach Kanten.........................................................................................43
3.9.2.2 Ändern von Attributwerten.....................................................................................43
3.9.2.3 Erzeugen von Knoten und Kanten..........................................................................43
3.9.2.4 Löschen von Knoten und Kanten...........................................................................44
3.10 Weiterführende Informationen.............................................................................................44
3.10.1 Weblinks und Dokumentation.........................................................................................44
3.10.2 Download und Lizenzinformation..................................................................................45
3.10.3 Referenzprojekte.............................................................................................................45
3.10.3.1 ReDSeeDS............................................................................................................46
3.10.3.2 SOAMIG..............................................................................................................46
3.10.3.3 COBUS.................................................................................................................46
4 Identifikation der notwendigen Anpassungen...............................................................................47
4.1 Motivation..............................................................................................................................47
4.2 Abgleich der Eigenschaften von Deklarationen und Referenzen..........................................47
4.3 Repräsentation impliziter Quelltext-Elemente.......................................................................53
4.3.1 Implizite super()-Aufrufe..................................................................................................53
4.3.2 Implizite Konstruktor-Definitionen..................................................................................53
4.3.3 Implizite Erweiterung von Object.....................................................................................54
4.3.4 Implizite Erweiterung von Enum......................................................................................54
4.3.5 Implizite Modifier im Kontext von Interfaces..................................................................54
6
4.3.6 Impliziter Modifier für Annotationstyp-Elemente............................................................55
4.3.7 Abgrenzung.......................................................................................................................56
4.3.7.1 Elemente ohne implizite Modifier..........................................................................56
4.3.7.2 Implizite Typkonvertierungen................................................................................56
4.3.7.3 Implizites this-Schlüsselwort.................................................................................57
4.4 Repräsentation der Override-Beziehung................................................................................57
4.5 Repräsentation statischer Bindungen.....................................................................................57
4.5.1 Methoden- und Konstruktoren-Aufrufe............................................................................58
4.5.2 Feld-Zugriffe.....................................................................................................................60
4.5.3 Ermittlung statischer Bindungen nach Faktenextraktion..................................................61
4.6 Referenzierung von Empfänger-Typen..................................................................................62
4.7 Fehlerhafte Lokationsinformation für Klassen......................................................................62
4.7.1 Abgrenzung.......................................................................................................................63
4.8 Repräsentation von Java-Elementen externer Bibliotheken..................................................63
4.9 Markierung der Veränderlichkeit von Quelltext-Elementen..................................................64
4.10 Rückreferenzierung des Quelltexts und Kommentare.........................................................64
5 Dokumentation der vorgenommenen Anpassungen......................................................................65
5.1 Beispiel für eine Anreicherung..............................................................................................65
5.2 Vorbereitung des TGraphen...................................................................................................66
5.3 Anreicherung der impliziten Quelltext-Elemente..................................................................66
5.4 Umsetzung der Override-Beziehung.....................................................................................67
5.5 Umsetzung der Referenzierung von Empfänger-Typen........................................................67
5.5.1 Einschränkungen der umgesetzten Anreicherung.............................................................67
5.6 Anpassung der Lokationsinformation für Klassen.................................................................68
5.7 Prototypische Umsetzung eines Bytecode-Faktenextraktors.................................................68
5.7.1 Nutzung von BCEL zum Einlesen des Bytecodes............................................................68
5.7.2 Repräsentierte Elemente und Beziehungen......................................................................69
5.7.2.1 Nur public und protected Elemente........................................................................69
5.7.2.2 Implizitheit wird nicht berücksichtigt....................................................................69
5.7.2.3 Klassen und Interfaces............................................................................................69
5.7.2.4 Methoden- und Variablen-Deklarationen...............................................................69
5.7.2.5 Supertyp-Beziehungen...........................................................................................70
5.8 mutable-Attribut zur Markierung der Veränderlichkeit.........................................................70
5.9 Reihenfolge der Anreicherung...............................................................................................70
6 Exemplarische Constraint-Generierung mittels TGraph...............................................................72
6.1 Laufzeitverbesserte Variante..................................................................................................74
7 Diskussion.....................................................................................................................................75
7.1 Allgemeine Eignung für die Entwicklungsarbeit...................................................................75
7.1.1 Verständlichkeit und Benutzbarkeit der Schnittstellen.....................................................75
7.1.1.1 Methoden zum Navigieren im TGraphen...............................................................75
7.1.1.2 Die Nutzung von GReQL.......................................................................................77
7.1.1.3 Util-Methoden........................................................................................................77
7
7.1.2 Verwendbarkeit des Metamodells.....................................................................................77
7.1.2.1 Wahl der Typ-Bezeichner.......................................................................................78
7.1.2.2 Kompositionen statt Aggregationen.......................................................................78
7.1.3 Reifegrad...........................................................................................................................78
7.2 Erweiterbarkeit.......................................................................................................................79
7.2.1 Anreicherung des TGraphen.............................................................................................79
7.2.2 Wartbarkeit des Faktenextraktors......................................................................................80
7.3 Performance...........................................................................................................................80
7.3.1 Rahmenbedingungen.........................................................................................................80
7.3.2 Laufzeit.............................................................................................................................81
7.3.3 Speicherverbrauch.............................................................................................................81
7.3.4 Vergleichsmessungen für verschiedene Quelltexte...........................................................82
7.3.5 Bewertung der Performance-Messungen..........................................................................83
8 Ausblick.........................................................................................................................................84
8.1 Notwendige Implementierungsarbeiten.................................................................................84
8.1.1 Neu-Implementierung der statischen Bindungen..............................................................84
8.1.2 Vervollständigung der Referenzierung von Empfänger-Typen.........................................84
8.1.3 Implementierung eines Bytecode-Faktenextraktors.........................................................85
8.1.4 Der GraBaJa-Faktenextraktor und künftige Java-Versionen............................................85
8.2 Über die Sichtbarkeitsconstraints hinaus...............................................................................85
8.2.1 Eignung für beliebige Constraint-Regeln.........................................................................85
8.2.2 Parallelen zur REFACOLA...............................................................................................86
9 Zusammenfassung.........................................................................................................................89
10 Anhang.........................................................................................................................................91
10.1 Installations- und Build-Hinweise zu JGraLab und GraBaJa..............................................91
10.2 Dokumentation des Erweiterungs-Projekts.........................................................................92
10.2.1 Struktur des Projekts.......................................................................................................92
10.2.2 Installation.......................................................................................................................93
10.2.3 Zahlen zum Umfang der Implementierung.....................................................................94
10.2.4 Demonstration mittels TGraph-Browser.........................................................................94
10.3 Versionsinformation.............................................................................................................98
8
Abbildungsverzeichnis
Abbildung 1: Einfacher Beispiel-TGraph..........................................................................................23
Abbildung 2: GUPRO-Architekturschema.........................................................................................25
Abbildung 3: Architekturübersicht GraBaJa / Graphenlabor.............................................................26
Abbildung 4: Metamodell: Programmwurzel und Dateibezug..........................................................29
Abbildung 5: Metamodell: Typ und Methoden-Definitionen.............................................................30
Abbildung 6: Metamodell: Subtyp-Beziehungen und Methoden-Aufrufe.........................................31
Abbildung 7: line- und offset-Attribute von Kanten..........................................................................32
Abbildung 8: Workflow bei der Umsetzung von TGraph-Anpassungen...........................................38
Abbildung 9: Heap-Monitoring während der Faktenextraktion des JUnit-Quelltexts.......................82
Abbildung 10: Entwicklungsprojekte.................................................................................................91
Abbildung 11: Import des Projekts.....................................................................................................91
Abbildung 12: Übersicht wesentlicher Implementierungs-Bestandteile............................................92
Abbildung 13: Konfiguration des Build Path.....................................................................................94
Abbildung 14: Argumente für den Start des TGraphBrowserServers................................................94
Abbildung 15: Startseite des TGraph-Browsers.................................................................................95
Abbildung 16: Struktur der Demonstrations-Beispiele......................................................................96
Abbildung 17: Anzeige während des TGraphen-Uploads..................................................................96
Abbildung 18: Tabellen-Ansicht des eingelesenen TGraphen...........................................................96
Abbildung 19: Visualisierungs-Modus des TGraph-Browsers...........................................................97
Abbildung 20: Generierte Dateien......................................................................................................98
9
Tabellenverzeichnis
Tabelle 1: Benötigte Eigenschaften von Deklarationen.....................................................................18
Tabelle 2: Benötigte Eigenschaften von Referenzen..........................................................................19
Tabelle 3: Abgleich der Eigenschaften von Deklarationen................................................................51
Tabelle 4: Abgleich der Eigenschaften von Referenzen.....................................................................53
Tabelle 5: Implizite super()-Aufrufe...................................................................................................53
Tabelle 6: Implizite Konstruktor-Definitionen...................................................................................53
Tabelle 7: Implizite Erweiterung von Object.....................................................................................54
Tabelle 8: Implizite Erweiterung von Enum.......................................................................................54
Tabelle 9: Implizite Modifier im Kontext von Interfaces...................................................................55
Tabelle 10: Impliziter Modifier für Annotationstyp-Elemente...........................................................56
Tabelle 11: Überprüfte Formen statischer Methoden-Bindung..........................................................59
Tabelle 12: Überprüfte Formen statischer Feld-Bindung...................................................................61
Tabelle 13: Laufzeit- und Speichermessungen für diverse Quelltexte...............................................82
10
1
Einleitung
Der Einsatz von Constraints für die Entwicklung von Refaktorisierungs-Werkzeugen hat sich
beginnend mit [TIP2003] in Bezug auf die Typ-Korrektheit von Java-Programmen und nachfolgend
in [STEI2009] für die korrekte Behandlung der Java-Sichtbarkeitsregeln als fruchtbar erwiesen.
Während für die Generierung der von Tip genutzten Type Constraints bereits effiziente
Implementierungen existieren, die auch Eingang in die Eclipse-Standarddistribution gefunden
haben, zeigte sich bei der Generierung der von Steimann vorgeschlagenen Sichtbarkeitsconstraints,
dass die hierfür benötigten Fakten nur umständlich und ineffizient aus der verwendeten FaktenBasis zu extrahieren waren.
Als Alternative zu den bisher erprobten Möglichkeiten der Fakten-Extraktion wird in der
vorliegenden Arbeit die Verwendung sogenannter TGraphen untersucht. Dabei handelt es sich um
eine Klasse von Graphen mit speziellen Eigenschaften, die sich ausgehend von [EBERT1995] als
besonders geeignet für die Repräsentation von Software-Systemen erwiesen haben. Die
Arbeitsgruppe um Ebert am Institut für Softwaretechnik der Universität Koblenz-Landau (IST)
entwickelte ein umfangreiches Framework zur Handhabung dieser Graphen, welches als
Ausgangspunkt für die vorliegende Arbeit genutzt wird. Insbesondere verspricht die in diesem
Framework integrierte Graphen-Abfragesprache GReQL Vorteile gegenüber dem
programmatischen Durchsuchen abstraker Syntaxbäume, das den bisherigen Implementierungen zu
Grunde lag.
Am Beispiel der Sichtbarkeitsconstraints wird gezeigt, dass die aus Java-Programmen generierten
TGraphen nicht alle Informationen enthalten, die für die Constraint-Generierung benötigt werden.
Die unzureichend repräsentierten Fakten werden spezifiziert, und sofern im Rahmen dieser Arbeit
die Implementierung einer erweiterten Faktenextraktion möglich war, wird diese dokumentiert. Um
auch künftigen Anpassungen den Weg zu bereiten, wird der Workflow, der dabei vom Entwickler zu
durchlaufen ist, detailliert beschrieben. Quelltext-Beispiele für vorgenommene Anpassungen und
eine exemplarische Constraint-Generierung sollen dem Leser selbst einen Eindruck der Eignung
von TGraphen vermitteln, bevor im Rahmen einer Diskussion die im Rahmen dieser Arbeit
gesammelten Erfahrungen aus Entwicklersicht dargestellt werden.
1.1
Aufbau der Arbeit
Nach dieser Einleitung werden im Kapitel 2. Der constraintbasierte Refaktorisierungsansatz
zunächst die Grundideen des Einsatzes von Constraint-Systemen im Rahmen von
Refaktorisierungen beschrieben. Es folgt eine detaillierte Spezifikation der sich ergebenden
Anforderungen an die zu repräsentierenden Java-Fakten am Beispiel der Sichtbarkeitsconstraints.
Das Kapitel 3. TGraphen zur Repräsentation von Java-Fakten beschreibt die allgemeinen
Eigenschaften von TGraphen und erläutert, warum diese für die Repräsentation von SoftwareSystemen zum Zwecke der Programmanalyse geeignet erscheinen. Es wird eine Übersicht der
wesentlichen Komponenten des vom IST bereitgestellten Frameworks zur Handhabung von
TGraphen gegeben. In detaillierteren Darstellungen wird auf das für die Java-Repräsentation zur
Verfügung stehende Metamodell und die Abfragesprache GReQL eingegangen.
Im Kapitel 4. Identifikation der notwendigen Anpassungen werden die Anforderungen an den
Informationsgehalt des TGraphen zur Generierung von Sichtbarkeitsconstraints mit den tatsächlich
repräsentierten Informationen abgeglichen. Die Notwendigkeit zur Repräsentation einer
11
Information auf der Basis der Anforderungen der Sichtbarkeitsconstraints wird dabei wo immer
möglich mittels der Java Language Specification belegt. Ist die jeweilige Information bereits in der
gewünschten Form im TGraphen enthalten, so wird dies durch einen Verweis auf die hierfür im
TGraphen zur Verfügung stehenden genutzten Knoten- und Kantentypen deutlich gemacht.
Zu Beginn des Kapitels 5. Dokumentation der vorgenommenen Anpassungen wird ein detailliertes
Beispiel gegeben, das die Vorgehensweise deutlich machen und einen Einblick in die
Entwicklungsarbeit mit TGraphen geben soll. Die weiteren Abschnitte dieses Kapitels beschreiben
die jeweils umgesetzten Lösungen.
Das Kapitel 6. Exemplarische Constraint-Generierung mittels TGraph schlägt die Brücke zur
eigentlichen Constraint-Generierung. Am Beispiel einer Constraint-Regel wird gezeigt, wie die
Extraktion der hierfür erforderlichen Fakten aus dem angepassten TGraphen erfolgen kann.
Die durchgeführten Implementierungsarbeiten werden im Kapitel 7. Diskussion aus Entwicklersicht
bewertet. Zu den untersuchten Qualitätskriterien zählen die Verständlichkeit, die Benutzbarkeit und
die Erweiterbarkeit sowie eine Einschätzung des Reifegrades des eingesetzten Frameworks. Zudem
werden die Ergebnisse einiger Performance-Messungen für die Extraktion größerer BeispielProjekte dargestellt.
Im Kapitel 8. Ausblick werden sowohl die kürzerfristig notwendigen Schritte wie auch die
längerfristigen Perspektiven aufgezeigt, die sich ergeben, wenn der Ansatz weiter verfolgt werden
soll. Das Kapitel 9. Zusammenfassung resümiert die Ergebnisse dieser Arbeit und gelangt zu einer
abschließenden Einschätzung der Eignung von TGraphen als Fakten-Basis für constraintbasierte
Refaktorisierungen.
12
2
Der constraintbasierte Refaktorisierungsansatz
Mit der steigenden Komplexität von Software-Systemen haben auch die Anforderungen an die
Werkzeuge zugenommen, die in der Software-Entwicklung eingesetzt werden. [MENS2004] weist
in diesem Zusammenhang auf die Bedeutung von Refaktorisierungs-Werkzeugen hin und beschreibt
wesentliche Qualitätskriterien, die für ihren Einsatz relevant sind. Wie [STEI2010] zeigt, existiert
jedoch bis heute – im Gegensatz etwa zum Compilerbau – keine zufriedenstellende Theorie, welche
als Grundlage für die Spezifikation von Refaktorisierungen und die darauf basierende
Implementierung von Werkzeugen dienen könnte.
Gängige Beschreibungen von Refaktorisierungen, wie sie z. B. in [FOWLER1999] gegeben
werden, folgen zumeist einem imperativen Ansatz. Für jede Refaktorisierung werden eventuelle
Vorbedingungen sowie die durchzuführenden Schritte aufgelistet, die eine erfolgreiche
Refaktorisierung gewährleisten sollen. Als Problem hat sich dabei die Vielzahl der nötigen
Fallunterscheidungen erwiesen, die erkannt und implementiert werden müssen, damit ein
Refaktorisierungs-Werkzeug in allen denkbaren Konstellationen zuverlässig arbeitet. Gemäß
[SCHÄFER2010-1] sind die auf diesem Ansatz basierenden Implementierungen häufig schwer zu
verstehen und zu warten.
Eine Alternative zu diesem imperativen Ansatz besteht in einer regelbasierten Herangehensweise,
wie sie beispielsweise in [PEREZ2009] beschrieben wird. Dabei kann der Quelltext in eine
Graphen-Repräsentation überführt werden, um die Refaktorisierung mittels Transformationsregeln
auf dem Graphen umzusetzen. Die Transformationsregeln sind deklarativer Natur und sollen so die
Probleme imperativer Ansätze vermeiden. Wie [STEI2010] zeigt, wurden in der Praxis zur
Umsetzung dieser Regeln jedoch häufig umfangreiche prozedurale Komponenten eingesetzt, so
dass der Unterschied zu imperativen Ansätzen an Bedeutung verlor.
Der constraintbasierte Refaktorisierungsansatz ist ebenfalls deklarativer Natur. [TIP2007] stellt
diesen Ansatz für einige Refaktorisierungen der Typen und Klassenhierarchien von JavaProgrammen vor. Er zeigt, wie durch die Einführung von Constraints die Typ-Korrektheit der
refaktorisierten Programme gewährleistet werden kann. In [STEI2009] legen Steimann und Thies
dar, wie dieser Ansatz zur korrekten Behandlung der Sichtbarkeits-Eigenschaft von Java-Elementen
bei verschiedenen Refaktorisierungen eingesetzt werden kann. [STEI2010] schlägt den
constraintbasierten Ansatz als mögliche theoretische Grundlage für den Bau von RefaktorisierungsWerkzeugen vor.
In den folgenden Abschnitten wird der constraintbasierte Refaktorisierungsansatz zunächst
allgemein, d. h. ohne den speziellen Bezug zu Sichtbarkeitsconstraints beschrieben. Die weiteren
Abschnitte dieses Kapitels behandeln am Beispiel der Sichtbarkeitsconstraints, welche
Anforderungen sich durch die notwendige Constraint-Generierung für die Repräsentation von JavaFakten ergeben. Abschließend wird gezeigt, welche Alternativen zur Repräsentation dieser Fakten
bestehen und welche Probleme von diesen Ansätzen bislang nicht zufriedenstellend gelöst werden.
13
2.1
Grundideen und -begriffe
2.1.1
Variablen
In einem constraintbasierten Ansatz werden Eigenschaften von Programmelementen in Form von
Bedingungen (engl. constraints) beschrieben. Zur Repräsentation solcher Eigenschaften dienen
Constraint-Variablen. So ist beispielsweise für Java-Programme die deklarierte Sichtbarkeit einer
Methode eine solche Eigenschaft, die durch eine Variable ausgedrückt werden kann. Für eine
konkrete Methode kann die deklarierte Sichtbarkeit festgestellt werden, indem der in der
Deklaration der Methode verwendete Access-Modifier (public, protected, private oder kein
Modifier) betrachtet wird. Ein weiteres Beispiel ist der Typ einer Variablen-Deklaration. Auch
dieser Typ kann festgestellt und einer Constraint-Variablen als Wert zugewiesen werden.
Die Änderung einer Variablenbelegung entspricht somit einer Programmänderung. Die Menge von
Programmänderungen, die für eine Refaktorisierung durchgeführt werden soll, lässt sich umgekehrt
als eine Menge geänderter Variablenbelegungen verstehen. Aus Constraint-Variablen entsteht ein
Constraint, also eine Einschränkung der möglichen Variablenbelegungen, indem verschiedene
Constraint-Variablen zueinander in Relation gesetzt werden. Die Art dieser Relationen wird im
folgenden Abschnitt beschrieben.
2.1.2
Relationen und Constraint-Regeln
Durch Relationen zwischen Constraint-Variablen kann ausgedrückt werden, dass die
Programmänderungen, die durch eine Refaktorisierung durchgeführt werden, nicht beliebiger Art
sein dürfen. Sie müssen vielmehr in Einklang mit der Java-Syntax stehen und dürfen über die
gewünschte Refaktorisierung hinaus keinerlei Änderung der Programm-Semantik bewirken.
Beispielsweise darf die Methode einer Subklasse, welche eine Methode ihrer Superklasse
überschreibt, nicht weniger sichtbar sein als die überschriebene Methode. Dies lässt sich wie folgt
ausdrücken:
[Sichtbarkeit der Methode der Subklasse] ≥ α [Sichtbarkeit der Methode der Superklasse]
In diesem Beispiel steht der ≥ α -Operator für die Relation der Sichtbarkeit der beiden Methoden.
Das tiefgestellte α drückt dabei aus, dass es sich um eine Relation von Sichtbarkeits-Eigenschaften
(engl. accessibility) handelt. Die beiden Sichtbarkeiten können einem konkreten Programm
entnommen und in den obigen Ausdruck eingesetzt werden, wodurch sich z. B. die folgende
konkrete Ausprägung ergeben könnte:
public
≥ α protected
Analog könnte eine Subtyp-Beziehung mittels T1 τ T2 dargestellt werden, um auszudrücken,
dass T1 ein Subtyp von T2 ist. Das tiefgestellte τ zeigt an, dass es sich um eine typbezogene
Relation handelt. Um aus diesem Ausdruck eine Constraint-Regel zu machen, müssen die konkreten
Typen T1 und T2 durch Constraint-Variablen ersetzt werden. Beispielsweise muss der Typ einer
Variablen, an die eine Zuweisung erfolgt, ein Subtyp oder identisch mit dem Typ des Ausdrucks
sein, der auf der rechten Seite der Zuweisung steht. Dies lässt sich wie folgt ausdrücken:
[Typ der Variablen, an die zugewiesen wird] ≤ τ [Typ des Ausdrucks auf der rechten Seite]
14
Für ein gegebenes syntaktisch korrektes Programm ist die durch solche Ungleichungen
ausgedrückte Bedingung immer wahr. Für die Sichtbarkeiten wird dabei als Ordnungsrelation
gemäß [JLS3] § 6.6.1 public > protected > default > private angenommen. Für die
Subtypen-Beziehung gelten die Eigenschaften, die in [JLS3] § 4.10 spezifiziert sind.
Würde eine Refaktorisierung dazu führen, dass die Sichtbarkeit der überschriebenen Methode auf
private reduziert wird, so wäre hingegen die nachfolgend ausgedrückte Bedingung nicht mehr
wahr. Die durchgeführte Refaktorisierung wäre demnach unzulässig.
private
≥ α protected
Welche Variablen und Relationen für eine Refaktorisierung zu einem gegebenen Programm erzeugt
werden, hängt zum einen von den syntaktischen und semantischen Eigenschaften der
Programmiersprache und zum anderen von den Anforderungen der jeweiligen Refaktorisierung ab.
Für jede Refaktorisierung ist die Anwendung einer bestimmten Auswahl von ConstraintGenerierungs-Regeln (auch kurz Constraint-Regeln, engl. constraint rules genannt) relevant. Die
Constraint-Regeln bilden somit den eigentlichen Kern des constraintbasierten
Refaktorisierungsansatzes, indem mit ihrer Hilfe das System aus Variablen und Relationen erzeugt
wird, das die Grundlage für die spätere Problemlösung bildet.
2.1.3
Constraints und Constrainterfüllung
Ein Constraint stellt einen Ausdruck aus Variablen und Relations-Operatoren dar, der für eine
Refaktorisierung aus einem gegebenen Programm generiert wurde. Ein Constraint ist somit
spezifisch für ein Programm und eine Eigenschaft, die im Rahmen der Refaktorisierung bewahrt
werden soll. Setzt man für ein syntaktisch korrektes Programm die konkreten Ausprägungen der
Variablen in den Ausdruck ein, so ist der gesamte Ausdruck wahr. Man sagt dann auch, das
Constraint sei für das Programm erfüllt (engl. constraint satisfaction). Die Menge aller generierten
Constraints wird als Constraint-System (engl. constraint set) bezeichnet. Sind alle Constraints eines
Constraint-Systems erfüllt, so wird im Folgenden das Constraint-System selbst als erfüllt
bezeichnet.
2.1.4
Das Finden einer constrainterfüllenden Refaktorisierung
Die Refaktorisierung muss so durchgeführt werden, dass nach dem Einsetzen der aus dem
geänderten Programm ermittelten neuen Ausprägungen aller Constraint-Variablen das ConstraintSystem weiterhin erfüllt ist. Das Finden einer constrainterfüllenden Refaktorisierung kann jedoch
auch umgekehrt erfolgen: Nach dem Aufbau des Constraint-Systems legt man die gewünschte
Programmänderung fest und wendet dann einen Standardalgorithmus zur Lösung des ConstraintSystems an. Findet dieser eine Lösung, so wird diese anschließend auf Quelltext-Ebene
zurückgeschrieben (engl. writing back the solution). Auf diese Weise können erprobte Algorithmen
zum Einsatz kommen, die im Rahmen der constraintbasierten Programmierung entwickelt wurden
(siehe z. B. [HOF2007]).
In der Praxis gibt der Benutzer zunächst seine gewünschte Programmänderung vor und legt somit
neue Ausprägungen für bestimmte Variablen des Constraint-Systems fest. Zusätzlich kann er ggf.
festlegen, dass die Werte weiterer Variablen unverändert bleiben müssen. Sind diese Vorgaben
gesetzt, so erledigt der Constrainterfüllungs-Algorithmus die weitere Arbeit. Dieser ist auch in der
Lage, weitere Abhängigkeiten aufzulösen: Wenn etwa das Ändern einer Constraint-Variablen zu
15
einer Constraint-Verletzung führen würde, die nur durch Änderungen weiterer Variablen geheilt
werden kann, so kann der Algorithmus diesen Prozess so lange fortsetzen, bis eine Lösung
gefunden ist. Kann keine Lösung gefunden werden, so ist das gestellte Refaktorisierungs-Problem
unlösbar.
2.2
Benötigte Java-Fakten für Sichtbarkeitsconstraints
In der vorliegenden Arbeit wird die Brauchbarkeit von TGraphen zur Repräsentation von JavaFakten für einen constraintbasierten Refaktorisierungsansatz am Beispiel der Sichtbarkeitsconstraints untersucht. Es wird darauf verzichtet, die dort beschriebenen Constraint-Regeln
nochmals formal vollständig wiederzugeben. Eine solche Zusammenfassung ist beispielsweise
bereits in [PAS2011] erfolgt. Die textuellen Beschreibungen aus dieser Arbeit bilden auch die
wesentliche Grundlage für den folgenden Abschnitt 2.2.1. Übersicht der Constraint-Regeln. Auf der
Basis dieser Regeln erfolgt im Abschnitt 2.2.2. Eigenschaften von Deklarationen und Referenzen
die Identifikation der Informationen, die zum Zwecke der Generierung von Sichtbarkeitscontraints
effizient ermittelbar sein müssen. In den verbleibenden Abschnitten dieses Kapitels werden noch
einige spezifische Faktentypen behandelt, die ggf. eine besondere Behandlung erfordern.
2.2.1
Übersicht der Constraint-Regeln
Ausgehend von [STEI2009] wurden für die Constraint-Regeln Kurzbezeichner eingeführt, die ihren
Ursprung in bestimmten Programmeigenschaften (wie z. B. Acc für Accessibility oder Inh für
Inheritence) haben. Diese Kurzbezeichner werden im Rahmen der folgenden Übersicht eingeführt
und im weiteren Verlauf der Arbeit referenziert, wenn auf bestimmte Constraint-Regeln verwiesen
werden soll.
•
•
•
•
•
•
Acc-1: Der Access-Modifier eines Deklarationselements muss gewährleisten, dass dieses
Element für alle Referenzen, die sich auf es beziehen, sichtbar ist.
Acc-2: Beim Zugriff auf als protected deklarierte nicht-statische Member oder
Konstruktoren eines Typen T muss sichergestellt werden, dass ein paketübergreifender
Zugriff nur durch Referenzen erfolgen darf, deren Typ eine Subklasse von T ist.
Inh-1: Auf geerbte Elemente muss genauso wie auf entsprechende Elemente der
Superklasse zugegriffen werden können.
Inh-2: Diese Constraint-Regel behandelt den Sonderfall zweier statisch deklarierter Felder,
deren unqualifizierter Name identisch ist. Bindet in einem solchen Fall eine Referenz an das
erste dieser Elemente und ist der Deklarationsort des anderen Elements auch Deklarationsort
einer der Superklassen der Referenz, so muss die Sichtbarkeit des ersten Elements größer
sein als die kleinste nötige Sichtbarkeit, um von der Referenz aus auf das andere Element
zugreifen zu können.
Sub-1: Überschreibt oder verdeckt eine Methode die andere, so muss der Access-Modifier
der einen größer oder gleich dem der anderen sein.
Sub-2: Implementiert eine Klasse Methoden eines Interfaces, so müssen diese Methoden
public deklariert sein.
•
Dyn-1: Eine überschriebene Methode der Superklasse muss in der Subklasse, in der sie
•
überschrieben wird, sichtbar sein. Nur dann kann eine dynamische Bindung entstehen.
Dyn-2: Stehen zwei Methoden-Deklarationen bei (ggf. unter Berücksichtigung von Type
Erasure) identischer Signatur nicht in einer Override-Beziehung, und ist der Deklarationsort
16
•
der ersten Methode eine der Lokationen der Superklassen des Deklarationsortes der zweiten
Methode, so muss die Sichtbarkeit der ersten Methode größer sein als die der zweiten.
Andernfalls würde eine Override-Beziehung entstehen, welche dynamische Bindung
ermöglicht.
Abs: Abstrakte Methoden dürfen nicht private deklariert sein.
•
Ovr: Ist die Referenz auf eine Methoden-Deklaration auch potentiell Referenz einer zweiten
Methoden-Deklaration, die im Sinne des statischen Bindealgorithmus von Java spezifischer
(siehe [JLS3] § 15.12.2.5), aber aktuell von der Referenz aus nicht sichtbar ist, so muss eine
Änderung der Sichtbarkeit jener zweiten Methoden-Deklaration sicherstellen, dass diese für
die Referenz weiterhin unsichtbar bleibt. Ansonsten entstünde eine Overload-Beziehung,
welche die Referenz statisch an die zweite statt an die erste Deklaration binden würde.
2.2.2
Eigenschaften von Deklarationen und Referenzen
Aus den beschriebenen Constraint-Regeln lässt sich die Notwendigkeit zur Ermittlung bestimmter
Eigenschaften von Deklarationen und Referenzen ableiten, die im Folgenden beschrieben werden.
Bei diesen Eigenschaften handelt es sich um Mengen von Java-Elementen, die ausgehend von der
jeweiligen Deklaration oder Referenz effizient ermittelbar sein müssen. Die folgenden beiden
Tabellen korrespondieren mit den Tabellen im Kapitel 4.2. Abgleich der Eigenschaften von
Deklarationen und Referenzen, in welchen die im TGraphen vorgefundene Information mit der hier
geforderten abgeglichen wird.
Eigenschaften von Deklarationen
Access-Modifier
static-Modifier für
Variablen-Deklarationen
abstract-Modifier für
Methoden-Deklarationen
Alle (auch implizite) Access-Modifier. Die Default-Sichtbarkeit (also
ein fehlender Access-Modifier) muss dabei nicht explizit repräsentiert
werden. Ebenso wenig ist für die Zwecke der vorliegenden Arbeit der
in [STEI2009] beschriebene absent-Modifier zu repräsentieren.
Der static-Modifier für Variablen-Deklarationen.
Der abstract-Modifier für Methoden-Deklarationen.
Identifier
Alle Identifier, d. h. alle im Rahmen von Deklarationen verwendeten
Bezeichner.
Typen
Alle Typen von Variablen, Rückgabetypen von Methoden, die Typen
der formalen Methoden-Parameter sowie Ausprägungen von
Typvariablen.
Lokationen
Alle Lokationen von Deklarationen, d. h. die Information darüber, an
welcher Stelle im Sinne eines voll qualifizierten Pfades sich eine
Deklaration befindet. Im Falle methodenlokaler Typdeklarationen ist
zudem die Methode inklusive ihrer geordneten Liste der formalen
Parameter in den voll qualifizierten Pfad einzubeziehen.
Formale Parameterliste
Die geordnete Liste der formalen Parameter für Methoden und
17
Eigenschaften von Deklarationen
Konstruktoren.
Supertyp-Beziehungen
Der Supertyp jedes Typen, d. h. für jede Klasse die Superklasse und
die implementierten Interfaces sowie für jedes Interface die
Superinterfaces.
Override-Beziehung
Override-Beziehungen zwischen Methoden-Deklarationen. An der
Override-Beziehung wird besonders deutlich, dass es nützlich sein
kann, die eigentlich bereits in der Fakten-Basis verfügbare
Information redundant, aber in effizient zugreifbarer Form zu
repräsentieren. Denn mittels der Supertyp-Beziehung und der
verfügbaren Methoden-Signaturen ließe sich die Override-Beziehung
gemäß [JLS3] § 8.4.8.1 jeweils auch programmatisch ermitteln. Wird
die Override-Beziehung jedoch bereits in der Fakten-Basis
repräsentiert, entfallen solche Aufwände.
Deklarations-Art
Die Art der deklarierten Elemente. In Java können folgende Elemente
deklariert werden: Klassen, Interfaces, Methoden, Konstruktoren,
Variablen, Enums, Annotationstypen, Elemente von Annotationstypen
und Typ-Parameter.
Menge der TypParameter
Die eventuelle Menge der Typ-Parameter bei Verwendung generischer
Datentypen.
Tabelle 1: Benötigte Eigenschaften von Deklarationen
Eigenschaften von Referenzen
Identifier
Alle Identifier, mit denen Deklarationen referenziert werden.
Lokationen
Die Lokationen, an denen die Referenzen stehen. Es gilt dabei das für
die Lokationen von Deklarationen Gesagte.
Statische Bindungen
Die Deklarationen, an die vom Compiler gebunden wird (statische
Bindungen).
Typ-Referenzen
Referenzen auf Typen kommen in Java für folgende Elemente vor
(Empfänger-Typen werden im folgenden Tabellen-Eintrag betrachtet):
•
•
•
•
•
Deklarationen von Methoden (Rückgabetyp, Typen der
Methodenparameter)
Deklarationen von Variablen
Deklaration von Typparametern
Deklaration von Arrays
Angabe von Interface-Typen im Rahmen der implements-
18
Eigenschaften von Referenzen
•
•
•
Empfänger-Typ
Klausel
Angabe von Supertypen im Rahmen der extends-Klausel
Angabe von Exception-Typen im Rahmen der throwsKlausel
im Rahmen der Objekt- oder Array-Erzeugung mittels new
Der Typ des Empfängers (engl. receiver) von Methoden-/KonstruktorAufrufen oder Feld-Zugriffen.
Tabelle 2: Benötigte Eigenschaften von Referenzen
2.2.3
Implizite Quelltext-Elemente
Über den explizit formulierten Quelltext hinaus enthalten Java-Programme eine Reihe impliziter
Quelltext-Elemente. Diese sind u. a. in [JLS3] spezifiziert und werden vom Java-Compiler in den
Java-Bytecode aufgenommen. Es handelt sich also um statische Quelltext-Eigenschaften, die somit
auch Relevanz für Refaktorisierungen haben. An dieser Stelle wird nur eine kurze Übersicht
gegeben. Eine detaillierte Beschreibung der für Sichtbarkeitsconstraints anzureichernden impliziten
Elemente wird im Rahmen des Abgleichs der notwendigen Anpassungen im Kapitel
4.3. Repräsentation impliziter Quelltext-Elemente gegeben.
•
Implizite Modifier: Einige Deklarationsarten – insbesondere im Kontext von Interfaces –
weisen implizite Modifier auf. So sind Interfaces immer abstract, ihre Methoden immer
public und abstract.
•
Implizite Ableitungen: Alle Klassen, die nicht explizit eine andere Klasse ableiten, leiten
implizit von Object ab. Alle Enum-Definitionen leiten implizit von Enum ab.
•
Implizite Konstruktor-Informationen: Ist in einer Klasse kein Konstruktor definiert, so
wird in dieser der implizite Default-Konstruktor angenommen. Jeder Konstruktor, der nicht
explizit seinen Superkonstruktor aufruft, ruft darüber hinaus implizit den parameterlosen
Konstruktor der Superklasse auf.
2.2.4
Öffentliche Eigenschaften externer Bibliotheken
Sind externe Bibliotheken (in Form von .class- oder .jar-Dateien, deren Quelltext nicht
vorliegt) in ein Java-Programm eingebunden, so können Eigenschaften dieser Bibliotheken, die im
Abschnitt 2.2.2. Eigenschaften von Deklarationen und Referenzen beschrieben wurden, relevant für
eine Refaktorisierung sein. Die Deklarationen in diesen Bibliotheken sind potentielles Ziel für
Referenzen oder Supertyp-Beziehungen. Referenzen in diesen Bibliotheken können potentiell,
wenn auch weniger häufig, auf Deklarationen im eigenen Quelltext verweisen.
19
2.2.5
Rückreferenzierung des Quelltexts und Kommentare
Sollen Änderungen des Java-Programms in den Quelltext zurückgeschrieben werden, so sind
zunächst die zu ändernden Quelltext-Stellen dort aufzufinden, wo sie tatsächlich geändert werden
sollen. Eine Änderung des Java-Programms innerhalb des TGraphen wäre zwar prinzipiell möglich,
dann wäre allerdings auch ein formatierungsbewahrender Mechanismus zum Zurückschreiben der
Änderungen erforderlich. Der Code-Generator für Java-Quelltext, der im Rahmen des GraBaJaProjekts implementiert wurde, verwirft jedoch die Formatierung und ist daher ohne weiteres Zutun
nicht für diesen Zweck geeignet. Naheliegend ist daher die Vorgehensweise, den TGraphen
lediglich für den Aufbau des Constraint-Systems zu nutzen, die berechneten Änderungen dann
jedoch mit den Mitteln der Entwicklungsumgebung, für welche die Refaktorisierung implementiert
wird, zurückzuschreiben. Bei einer Implementierung für Eclipse würde hierfür die Nutzung des
abstrakten Syntaxbaums (engl. Abstract Syntax Tree, kurz AST) naheliegen, der von den in Eclipse
integrierten Java Development Tools (siehe [URL:JDT]) bereitgestellt wird und der über eine
entsprechende Rewrite-Funktionalität (siehe [URL:ASTREWRITER]) verfügt. Um diejenigen
Elemente, die im AST geändert werden müssen, effizient auffinden zu können, wäre die
Repräsentation einer Positionsinformation dieser Elemente hilfreich.
Im Kontext der Rückreferenzierung des Quelltexts kann auch die Repräsentation von Kommentaren
betrachtet werden. Da im Rahmen der Durchführung von Refaktorisierungen ggf. auch Bezeichner
oder Pfade in Kommentaren angepasst werden sollen (dies wird von einigen RefaktorisierungsWerkzeugen über einen Preview-Dialog unterstützt), wäre es nützlich, die Kommentare ebenfalls im
TGraphen zu repräsentieren.
2.3
2.3.1
Bisherige Ansätze zur Repräsentation der Fakten
Repräsentation als Eclipse-AST
Die Java Development Tools des Eclipse-Projekts stellen ein Framework zum Einlesen von JavaQuelltext in einen AST zur Verfügung, welches die Grundlage zahlreicher Werkzeuge der EclipseIDE ist, darunter auch einige der integrierten Refaktorisierungs-Werkzeuge. Der eingelesene AST
repräsentiert das jeweilige Java-Projekt vollständig und korrekt und stellt eine etablierte API für
Abfragen und Manipulationen zur Verfügung. Es verwundert daher nicht, dass diese
Repräsentationsform auch gewählt wurde, um den constraintbasierten Refaktorisierungsansatz
exemplarisch umzusetzen, so etwa in [KEG2007] und [STEI2009]. Dabei zeigten sich jedoch einige
Nachteile:
•
•
2.3.2
Es bestehen nur unzureichende Möglichkeiten, den AST um Informationen anzureichern, die
speziell für die Constraint-Generierung häufig benötigt werden.
Einige Informationen erwiesen sich, obwohl prinzipiell im AST vorhanden, als nur
umständlich oder ineffizient extrahierbar, da die Art der Abfragen, die für die ConstraintGenerierung genutzt wird, nur schwer über die API des AST abzubilden war.
Erweiterte Faktenextraktion und Speicherung in Hashtabellen
Die im vorherigen Absatz beschriebenen Probleme führten zu einer erweiterten Faktenextraktion
auf der Basis des Eclipse-AST, welche die für die Constraint-Generierung wichtigsten
Informationen in Form von Hashtabellen aufbaute und eine rudimentäre API zur Verfügung stellte,
20
um die jeweiligen Abfragen zu stellen. Als problematisch erwiesen sich bei diesem Ansatz das
Laufzeitverhalten und der Speicherverbrauch. So dauerte die Faktenextraktion für einige
Beispielprojekte mehrere Minuten und benötigte so viel Speicherplatz, dass diese Problemlösung
für Refaktorisierungs-Werkzeuge als nicht praktikabel erscheint. Des Weiteren ist die genutzte API
zum Stellen der Abfragen sehr spezifisch und müsste für weitere Constraint-Generierungen nach
und nach ergänzt werden, was die Erweiterbarkeit des gesamten Ansatzes in Frage stellt. Auch das
parallele Vorhalten von Informationen über den Java-Quelltext in zwei unterschiedlichen
Datenstrukturen (AST und Hashtabellen) erscheint architektonisch als wenig elegant und würde
künftige Erweiterungen erschweren.
2.3.3
Nutzung eines Java-Compilers
In [EKMAN2008] weisen Ekman und Kollegen auf die Problemstellung hin, dass ein Großteil der
Arbeit bei der Implementierung von Refaktorisierungen in der Analyse der Quelltext-Elemente
besteht. Sie stellen fest, dass diese Analysen in vielerlei Hinsicht solchen Analysen ähneln, die in
Compilern implementiert sind, und schlagen daher die Implementierung einer Refactoring-Engine
auf der Basis eines erweiterbaren Compilers (JustAddJ, siehe [URL:JUSTADDJ]) vor. Sie zeigen,
dass dieser Ansatz erfolgreich für die Refaktorisierungen „Rename“, „Extract Method“ und
„Encapsulate Field“ funktioniert.
Dieser Ansatz ist auch deshalb interessant, weil in weiteren Arbeiten dieser Forschungsgruppe,
darunter [SCHÄFER2010-1], darauf hingewiesen wird, dass sich imperative
Refaktorisierungsansätze als zu komplex und schwer wartbar erwiesen haben, weshalb dort eine
abstraktere Spezifikationssprache vorgeschlagen und auf Basis der Compiler-basierten RefactoringEngine deren Funktionsfähigkeit exemplarisch gezeigt wird.
Die Bewertung einer möglichen Nutzung des Compiler-basierten Ansatzes für den hier
beschriebenen contraintbasierten Refaktorisierungsansatz vermag im Rahmen der vorliegenden
Arbeit nicht geleistet zu werden. Die genannten Arbeiten legen nahe, dass diese Herangehensweise
zumindest für die Extraktion der rein programmiersprachlichen Fakten durchaus tragfähig sein
könnte.
Allerdings ist anzumerken, dass ein Compiler ausschließlich sprachspezifische Eigenschaften
unterstützen kann und in dieser Hinsicht weniger Flexibilität als ein TGraph-basierter Ansatz bietet.
TGraphen ermöglichen grundsätzlich progammiersprachenübergreifende Repräsentationen und
wären auch in der Lage, weitere Artefakte wie z. B. XML-Spezifikations-Dateien für Dependency
Injection Container á la Spring oder EJB 3 zu verarbeiten, wie sie heute in produktiven
Umgebungen häufig im Einsatz sind.
2.3.4
Weitere Graphen-basierte Ansätze
[RENSINK2009] beschreibt die Konstruktion eines typisierten Graphen zur Repräsentation von
Java-Programmen. Für den Aufbau des Graphen wird der Eclipse-AST genutzt. Der aufgebaute
Graph ist zwar typisiert, ihm fehlen jedoch die für TGraphen geforderte Ordnung und Attributierung
der Knoten und Kanten. Der resultierende Graph kann insgesamt als eine deutlich schlankere
Version eines TGraphen angesehen werden, zumal die umfangreichen und ausgereiften
Möglichkeiten des Graphenlabors weitgehend fehlen. Die genannte Arbeit unterstreicht daher
lediglich den Bedarf für eine leistungsfähige Graphenrepräsentation von Java-Quelltext.
21
2.3.5
Sonstige Alternativen
Im Artikel „A Generic System to Support Multi-Level Understanding of Heterogeneous Software“
aus [GUPRO1998] nennen Ebert und Kollegen einige weitere vorgeschlagene Alternativen zur
Repräsentation von Software, ohne diese im Detail zu diskutieren:
•
•
•
•
Relationale Datenbanken [CHEN1990]
PROLOG-Datenbanken [JARZ1995]
LISP images [WELLS1995]
Hybride Wissensdatenbanken (engl. hybrid knowledge bases) [JARZ1995]
Die von Ebert genannten Argumente, die gegenüber diesen Ansätzen für TGraphen sprechen,
werden im Kapitel 3.8. Welche Vorteile können erhofft werden? wiedergegeben.
Eine weitere Alternative, die im Rahmen der vorliegenden Arbeit nicht näher untersucht wurde,
bildet das gemäß [URL:JTRANSFORMER] als query and transformation engine for Java source
code bezeichnete JTransformer. Dieses arbeitet intern mit einer AST-Repräsentation auf Basis einer
PROLOG-Datenbank. Ohne eingehendere Untersuchung können die Möglichkeiten dieses
Werkzeugs gegenüber TGraphen allerdings nicht abgeschätzt werden.
2.3.6
Zusammenfassung der Probleme der bisherigen Ansätze
Mit den bisherigen Lösungsansätzen zur Repräsentation von Java-Fakten konnte grundsätzlich die
Funktionsfähigkeit des constraintbasierten Refaktorisierungsansatzes gezeigt werden, jedoch wiesen
all diese Ansätze Probleme auf, welche einer Umsetzung in produktiven Entwicklungsplattformen
und damit der in [STEI2010] beschriebenen Idee, zahlreiche oder gar alle RefaktorisierungsWerkzeuge einer IDE auf einer einheitlichen, constraintbasierten Infrastruktur aufzusetzen, im
Wege stünden. Die in der vorliegenden Arbeit untersuchte Repräsentation von Java-Fakten mittels
TGraphen wird sich daran messen lassen müssen, ob sie diese Probleme zu lösen imstande wäre.
Deshalb werden diese Probleme hier nochmals kurz zusammengefasst:
•
•
•
•
Abfrage von Information: Die Abfrage der benötigten Information muss entweder über
umständliches Traversieren der bereitstehenden Datenstruktur (AST) oder über eine zu
spezielle API erfolgen. Es existiert keine einheitliche Abfragesprache, welche allen
bekannten und künftigen Constraint-Generierungen zu Grunde liegt.
Anreicherung der Daten: Die Anreicherung der gewählten Datenstruktur mit zusätzlicher
benötigter Information ist schwierig.
Einheitliche Datenstruktur: Die Information wird mitunter in zwei unterschiedlichen
Datenstrukturen vorgehalten.
Performance: Die Faktenextraktion erweist sich als zu speicher- und zeitaufwändig.
Inwieweit diese Probleme durch TGraphen vermieden werden können, wird im Abschnitt 3.8.1.
Lösung der Probleme anderer Ansätze untersucht. Eine grobe Einschätzung der Performance von
Faktenextraktionen für TGraphen gibt das Kapitel 7.3. Performance.
22
3
TGraphen zur Repräsentation von Java-Fakten
3.1
Bezeichnung
TGraphen bestehen wie alle Graphen aus einer Knoten- und Kantenmenge sowie einer
Inzidenzfunktion, welche jeder Kante ein Knotenpaar zuordnet. Der Begriff des „TGraphen“ wurde
erstmalig in [EBERT1995] eingeführt. Das vorangestellte „T“ zeigt die Typisierung der Knoten und
Kanten an. Auch wenn die besonderen Eigenschaften von TGraphen, die im folgenden Abschnitt
beschrieben werden, rein formaler Natur sind und in diesem Sinne eine spezielle Graphenklasse im
Sinne einer bloßen mathematischen Struktur kennzeichnen, wird der Begriff des „TGraphen“ bis
heute ausschließlich im Kontext der Forschungs- und Entwicklungsarbeiten der Arbeitsgruppe um
Ebert verwendet. Der Begriff ist daher weniger als anerkannter formal-mathematischer Fachbegriff
zu verstehen, sondern vielmehr eine Art Markenname für die von Ebert und Kollegen zur
Repräsentation von Software-Systemen eingesetzten Graphen und die dabei verwendeten
Werkzeuge.
3.2
Formale Eigenschaften von TGraphen
TGraphen weisen die folgenden formalen Eigenschaften auf:
•
•
•
•
3.3
Sie sind typisiert, d. h. jeder Knoten und jede Kante weist einen Typ auf.
Sie sind attributiert, d. h. jeder Knoten und jede Kante kann über ein oder mehrere
Attribute verfügen, die je nach Typ des Attributs unterschiedliche Ausprägungen annehmen
können.
Sie sind gerichtet, d. h. jede Kante verbindet einen Startknoten mit einem Endknoten.
Sie sind in mehrfacher Hinsicht geordnet: Alle Knoten des gesamten Graphen bilden eine
Reihenfolge. Alle Kanten des gesamten Graphen bilden eine Reihenfolge. Alle inzidenten
Kanten zu einem Knoten bilden eine Reihenfolge.
Beispiel für einen TGraphen
Die folgende Abbildung zeigt drei dieser vier Eigenschaften:
Abbildung 1: Einfacher Beispiel-TGraph
23
Dieser Beispiel-TGraph repräsentiert einen kleinen Ausschnitt aus dem Straßennetz. Die Knoten
repräsentierten Städte und können unterschiedliche Typen wie „Landeshauptstadt“, „Stadt“ oder
„Kleinstadt“ annehmen. Die Kanten repräsentieren verbindende Straßen der Typen „Autobahn“ und
„Bundesstrasse“. Die Namen von Städten und Straßen werden über ein Attribut angegeben. Die
Richtung der Kanten drückt sich in der Pfeilspitze aus (auf die Rückrichtung wurde hier jeweils
verzichtet). Die Ordnung des Graphen ist der Abbildung nicht zu entnehmen und kann als interne
Verwaltungsinformation betrachtet werden.
3.4
Repräsentation von Software-Systemen mittels TGraphen
[EBERT2010] fasst zusammen, warum Graphen grundsätzlich zur Repräsentation von SoftwareSystemen geeignet sind: Jede Entität eines Software-Systems kann als Knoten dargestellt werden,
während jede Beziehung zwischen diesen Entitäten durch eine Kante gebildet werden kann.
Grundsätzlich ist die Art der Entität dabei nicht auf Quelltext-Elemente beschränkt, sondern kann
auch weitere Artefakte wie etwa Dateien oder Testfälle umfassen, so dass Graphen eine
leistungsfähige und programmiersprachenübergreifende Möglichkeit zur Modellierung von
Software in unterschiedlichen Kontexten darstellen.
Nutzt man zusätzlich die formalen Eigenschaften von TGraphen, so wird diese Darstellungsform
gemäß [EBERT2002] noch leistungsfähiger :
•
•
•
•
Durch Attribute können Knoten und Kanten mit zusätzlichen Informationen angereichert
werden.
Durch Typisierung von Knoten und Kanten können unterschiedliche Entitäten des
Software-Systems auf einfache Weise voneinander unterschieden werden. Sie ermöglicht
eine Modellierung, welche Entitäten zu welchen anderen Entitäten überhaupt in Beziehung
stehen dürfen. Zudem lassen sich für unterschiedliche Entitäten unterschiedliche
Attributmengen definieren.
Gerichtete Kanten ermöglichen die präzisere Modellierung von Beziehungen zwischen
Entitäten. Des Weiteren setzt eine Reihe effizienter Graphen-Algorithmen gerichtete Kanten
voraus. Zu erwähnen ist jedoch an dieser Stelle, dass die Implementierung von TGraphen
innerhalb des Graphenlabors, die nachfolgend noch ausführlich beschrieben wird, trotz
gerichteter Kanten auch eine Traversierung in der jeweiligen Gegenrichtung einer Kante
ermöglicht. Dies erleichtert die Entwicklungsarbeit mit TGraphen und ermöglicht auch den
Einsatz von Algorithmen für ungerichtete Graphen.
Durch eine definierte Ordnung lassen sich auf natürliche Weise Sequenzen (z. B. von
Anweisungen, Parametern usw.) darstellen, die ansonsten weniger elegant über
Positionsindizes oder andere Hilfsmittel realisiert werden müssten. Zudem ermöglicht die
Ordnungseigenschaft ebenfalls die Implementierung deterministischer GraphenAlgorithmen.
Damit stellen TGraphen aus formaler Sicht eine leistungsfähige Repräsentationsform beliebiger
Software-Systeme dar. Sie sind nicht auf eine spezielle Programmiersprache oder überhaupt auf
Quellcode beschränkt, sondern sind in der Lage, die Komplexität moderner Software-Systeme in
einer einheitlichen, gut untersuchten Struktur darzustellen, die mit algorithmischen
Standardverfahren behandelt werden kann.
24
3.5
Kurze Entwicklungsgeschichte
Das Konzept einer auf TGraphen basierenden Modellierung nicht nur von Programm-Quelltext,
sondern von Software-Systemen im Allgemeinen wurde erstmalig im Jahr 1995 von Ebert und
Franzke in [EBERT1995] vorgestellt. Die Ausarbeitung dieses Konzepts erfolgte in den folgenden
Jahren in Form eines „Graphenlabors“ (GraLab), welches u. a. ausführlich in Form eines
Benutzerhandbuchs in [DAHM1998] dokumentiert wurde. Das Graphenlabor stellte für sich
genommen eine leistungsfähige Schnittstelle zur Verwendung von TGraphen als Datenstruktur
bereit und war in C++ implementiert. Im Rahmen der Diplomarbeit [KAHLE2006] wurde das
Graphenlabor um eine in Java implementierte Version namens JGraLab ergänzt. Beide Versionen
werden bis heute offiziell von der Arbeitsgruppe Ebert betreut. Aktuelle Weiterentwicklungen und
Projekte werden jedoch ausschließlich auf Basis von JGraLab durchgeführt. Alle weiteren
Ausführungen der vorliegenden Arbeit beziehen sich auf JGraLab.
Parallel zur Entwicklung der konzeptionellen Grundlagen von TGraphen und der Implementierung
des Graphenlabors war das IST ab Mitte der neunziger Jahre am Projekt GUPRO („Generische
Umgebung zum Programmverstehen“) beteiligt, welches gemäß [EBERT1996] die
programiersprachenübergreifende Entwicklung von Programmverstehens-Werkzeugen zum Ziel
hatte. Dabei sollte die Repräsentation von Programm-Quelltexten möglichst flexibel erfolgen, um
auch unvorhersehbare Analyseanforderungen mit minimalen Aufwand umsetzen zu können.
GUPRO baute auf den Erfahrungen mit TGraphen auf und schuf u. a. eine Graphen-Repräsentation
für COBOL. Dabei war der GUPRO-Ansatz allgemeinerer Natur: Es sollten nicht nur
Programmiersprachen, sondern auch Jobkontrolltexte, Datenbankbeschreibungen und andere
Dateiinhalte repräsentierbar sein. In GUPRO existiert daher das zentrale Konzept eines
Repositories, in welchem die unterschiedlichen zu repräsentierenden Inhalte in einheitlicher Form
vorgehalten und analysiert werden können. Dies zeigt die folgende Grafik aus [GUPRO1998]:
Abbildung 2: GUPRO-Architekturschema
25
Während das eigentliche Projekt 1998 abgeschlossen war, wurden die daraus entstandenen
Komponenten auch anschließend vom IST weiterentwickelt und um neue Komponenten ergänzt.
Heute kommen gemäß [URL:GUPRO] insbesondere Graphen-Repräsentationen für C- und AdaProgramme zum Einsatz (siehe auch 3.8.4.Programmiersprachenübergreifende Refaktorisierungen).
Da für GUPRO die analytischen Aspekte für die Repräsentation des Quelltexts im Vordergrund
standen, wurde die Abfragesprache GReQL („GUPRO-Repository Query Language“) entworfen
und auf der Basis von GraLab und JGraLab implementiert. Diese Arbeiten begannen mit
[KAMP1996], die javabasierte Version wurde in [BILD2006] entworfen und in den folgenden
Jahren ausimplementiert. GReQL erweitert somit das Graphenlabor um leistungsfähige AbfrageMöglichkeiten zur Analyse von TGraphen.
Die Nutzung von TGraphen und des Graphenlabors für die Repräsentation von Java-Quelltext
wurde im Rahmen der Studienarbeit [BALD2008] geleistet. Dort wird der eigentliche
Faktenextraktor sowie das Metamodell für Java-Quellcode beschrieben. Diese Komponente wird
heute unter der Bezeichnung GraBaJa („Graph Based Java“) am IST gewartet.
3.6
Architekturübersicht zu GraBaJa und dem Graphenlabor
Für die Zwecke der vorliegenden Arbeit ist insbesondere die Repräsentation von Java-Quelltext
relevant, die durch die GraBaJa-Komponente auf der Basis des Graphenlabors geleistet wird.
Zudem ist die Abfragesprache GReQL von besonderer Bedeutung. Die folgende Abbildung
veranschaulicht die wesentlichen Komponenten und ihre Zusammenhänge:
Abbildung 3: Architekturübersicht GraBaJa /
Graphenlabor
26
Die vom Graphenlabor angebotenen Funktionen, die im Rahmen dieser Arbeit genutzt werden,
umfassen insbesondere folgende:
•
•
•
•
Bereitstellung der Basisklassen, von denen alle speziellen Graphen-Implementierungen
ableiten müssen, und damit auch der Basisfunktionen für die Implementierungsarbeit mit
TGraphen.
Ein Code-Generator zur Erzeugung der Graph-Klassen nach Anpassung des GraBaJaMetamodells. Im generierten Code werden insbesondere auch Factory- und SchnittstellenMethoden bereitgestellt, welche die Erzeugung und Verwendung neuer Knoten- und
Kantentypen erleichtern.
Utilities, insbesondere Export- und Konvertierungsfunktionen für das Abspeichern
eingelesener Graphen in verschiedenen Datei-Formaten. Beispielsweise hat sich das DOTFormat (siehe [URL:DOT]) für die visuelle Untersuchung repräsentierter
Programmfragmente als hilfreich erwiesen. Zu diesem Zweck steht auch ein TGraphBrowser zur Verfügung, mit dessen Hilfe in einem erzeugten TGraphen interaktiv navigiert
werden kann.
Die Möglichkeit zur Nutzung von GReQL-Abfragen, d. h. Parser, Auswerter und Optimierer
für die GReQL-Abfragesprache.
GraBaJa stellt für die Zwecke dieser Arbeit insbesondere folgende Funktionen bereit:
•
•
3.7
3.7.1
Das in einem speziellen Dateiformat spezifizierte Metamodell, welches die für JavaQuelltexte spezifischen Knoten- und Kantentypen sowie deren Beziehungen und Attribute
beschreibt.
Einen Faktenextraktor mit entsprechenden Schnittstellen-Methoden für das Einlesen von
Dateien, um die TGraphen-Repräsentation eines Java-Projekts zu erzeugen. Die Interna des
Faktenextraktors werden im Rahmen dieser Arbeit nicht modifiziert, da die Modifikation
des TGraphen ausschließlich über Metamodell-Anpassungen und nachträgliche
programmatische Anreicherungen erfolgt.
Einige Eigenschaften im Detail
Faktenextraktion
Die Faktenextraktion für den TGraphen basiert im Wesentlichen auf einem mittels des
Parsergenerators ANTLR auf Basis einer ausgewählten Javagrammatik erzeugten Parser (für Details
zur Verwendung von ANTLR innerhalb des Faktenextraktors siehe [BALD2008], S. 12ff). Die
Faktenextraktion ist ebenfalls in der genannten Arbeit im Kapitel „7. Funktionsweise des
Javaextraktors“ ausführlich beschrieben. Zusammenfassend wird dabei ausgehend von einem
mittels des generierten Parsers erzeugten AST ein TGraph aufgebaut, der zunächst nur die
syntaktische Information des AST enthält. In weiteren Verarbeitungsschritten wird dieser TGraph
um Dateiinformationen sowie einige Zusatzinformationen wie Variablenzugriffe und MethodenAufrufe ergänzt. Zudem werden ggf. einige weitere Informationen über die ReflectionFunktionalität von Java ermittelt und in den TGraphen integriert.
27
3.7.2
Metamodell
Das Metamodell wird in einem speziellen Dateiformat definiert, das vom Graphenlabor vorgegeben
ist. Die Spezifikationsdatei wird im Folgenden kurz als „Schemadatei“ bezeichnet. In der
Terminologie von [BALD2008] wird der Begriff des Metamodells auf die UML-Repräsentation der
Schemadatei angewendet. Diese Einschränkung soll hier nicht getroffen werden, da die Pflege der
UML-Repräsentation grundsätzlich seit Abschluss der genannten Arbeit nicht konsequent
fortgeführt wurde und nur mit einem speziellen kommerziellen Werkzeug möglich ist (siehe auch
3.9.1. Anpassung des Metamodells). In der vorliegenden Arbeit werden mit dem Begriff des
Metamodells somit allgemeiner alle für einen TGraphen verwendbaren Knoten- und Kantentypen
und deren Eigenschaften bezeichnet.
Das spezielle Metamodell, das im Rahmen von GraBaJa entwickelt wurde, definiert alle Knoten
und Kantentypen zur Repräsentation von Java-Quelltext, deren mögliche Attribute, ihre eventuelle
Vererbungsbeziehung sowie die Richtung der Kanten. Details zum Entwurf des GraBaJaMetamodells sind im Kapitel „5.4 Eigenes Metamodell für Java 5“ sowie in „Anhang A.
Metamodell der Graphklassen“ in [BALD2008] beschrieben. An dieser Stelle soll ein Einblick
gegeben werden.
Der durch das GraBaJa-Metamodell spezifizierte TGraph und seine Eigenschaften nach der
Faktenextraktion wird im Folgenden sowie im weiteren Verlauf der Arbeit als „GraBaJa-TGraph“
bezeichnet.
3.7.2.1
Einfacher Beispiel-Quelltext
Der folgende einfache Beispiel-Quelltext dient zur Veranschaulichung des Metamodells. Die Zeilen
sind dateiübergreifend durchnummeriert. Die Zeilennummern werden bei der Beschreibung des
TGraphen jeweils in Klammern referenziert. Da bereits simple Java-Programme wie dieses zu
relativ komplexen Graphen führen, werden für unterschiedliche Teile des Metamodells in der Folge
verschiedene Ausschnitte aus dem generierten TGraphen gezeigt.
Datei Superklasse.java:
1
2
3
4
package testsources.not4autotests;
class Superklasse {
public void superklassenmethode() {}
}
Datei Subklasse.java:
5 package testsources.not4autotests;
6 class Subklasse extends Superklasse {
7
void subklassenmethode() {
8
superklassenmethode();
9
}
10 }
28
3.7.2.2
Programmwurzel und Dateibezug
Der folgende Ausschnitt zeigt die Verankerung des Java-Programms im Dateisystem. Es wird nur
die Repräsentation von Superklasse.java gezeigt.
Abbildung 4: Metamodell: Programmwurzel und Dateibezug
Jeder GraBaJa-TGraph enthält einen Program-Knoten, von dem aus sich der Bezug zum
Dateisystem aufspannt. Über die Abstraktion TranslationUnit sind die geparsten Dateien
(SourceFile) und ihre Verwender (SourceUsage) in den Graphen eingebunden. Letztere bilden
die Brücke zum eigentlichen Java-Quelltext. Dem Program-Knoten ist auch die Paket-Hierarchie
(JavaPackage) zugeordnet.
29
3.7.2.3
Typ- und Methoden-Definitionen
Abbildung 5: Metamodell: Typ und Methoden-Definitionen
Typ- und Paket-Definitionen sind über eine IsExternalDeclarationIn-Kante an das
Dateisystem angeknüpft. Die PackageDefinition (Zeile 1) ordnet der Datei das Paket zu, dem
sie angehört. Die in diesem Beispiel definierte Klasse beginnt mit dem Knoten ClassDefinition
(Zeile 2) und weist einen Block (Zeilen 2 bis 4) auf. In diesem sind die Member der Klasse über
IsMemberOf-Kanten referenziert. Das Beispiel enthält lediglich eine MethodDefinition (Zeile
3).
30
3.7.2.4
Subtyp-Beziehungen und Methoden-Aufrufe
Abbildung 6: Metamodell: Subtyp-Beziehungen und Methoden-Aufrufe
Subtyp-Beziehungen zwischen Klassen werden über eine IsSuperClassOfClass-Kante (Zeile 6)
repräsentiert. Für die Beziehungen zwischen Klassen und Interfaces sowie zwischen Interfaces und
Interfaces existieren analoge Kantentypen (IsInterfaceOfClass, IsInterfaceOf).
Für Methoden-Aufrufe existiert der Knotentyp MethodInvocation (Zeile 8). Dieser wird durch
eine Kante des Typs IsDeclarationOfInvokedMethod an einen Knoten des Typs
MethodDefinition geknüpft. Dieser Kanten-Typ wird im Kapitel 4.5. Repräsentation statischer
Bindungen ausführlich untersucht.
31
3.7.2.5
Rückreferenzierung des Quelltexts
Zur Rückreferenzierung von TGraphen-Instanzen auf den ursprünglichen Quelltext existieren im
TGraphen zwei Attribute an jeder Kante: Ein line-Attribut gibt die Zeile des jeweiligen QuelltextElements innerhalb der TranslationUnit an, in welcher das Element enthalten ist. Und ein
offset-Attribut gibt die Anzahl der Zeichen an, die das jeweilige Element vom Dateibeginn
entfernt steht. Für die Klasse
package testsources.not4autotests;
public class EinfacheKlasse {
}
wird der folgende TGraph generiert, dessen Kanten-Attribute hier mit ausgegeben werden (was aus
Platzgründen bei den anderen TGraph-Abbildungen bisher nicht der Fall war):
Abbildung 7: line- und offset-Attribute von Kanten
Bei Änderungen des TGraphen bleiben diese Attribute (wenn sie nicht explizit modifiziert werden)
unverändert und spiegeln somit die Positionsinformation der Elemente im ursprünglichen Zustand
wider. Diese kann etwa genutzt werden, um die entsprechenden Elemente im Eclipse-AST
aufzufinden, der ebenfalls eine Offset-Information verwaltet (siehe hierzu die Methode
getSourceRange() in [URL:ISOURCEREF]).
32
Anmerkung: Zählt man für das obige Beispiel den Offset, d. h. die Anzahl der Zeichen vom
Dateibeginn bis beispielsweise zum public-Modifier, so stellt man eine Abweichung fest:
Während der TGraph 38 Zeichen zählt, so kommt man durch manuelles Zählen lediglich auf 36
Zeichen – jedenfalls dann, wenn man auf einem Windows-Betriebssystem arbeitet und die JavaDatei dort erzeugt hat. Dieser Unterschied ergibt sich daraus, dass die Funktion zur Berechnung des
Offsets für den Zeilenumbruch die tatsächlich codierten Zeichen zählt. Unter Windows sind dies
jedoch zwei Zeichen (carriage return und line feed), während es beispielsweise unter Unix nur
eines (line feed) ist. Generiert man Java-Datei und TGraph unter Unix, so wird als Offset tatsächlich
36 berechnet.
3.7.3
Die Abfragesprache GReQL
3.7.3.1
Bezeichnung, Komponenten und Geschichte
Der Name „GReQL“ leitet sich aus der Langbezeichnung „GUPRO-Repository Query Language“
ab. Als Teil des Graphenlabors existiert von GReQL eine C++-Implementierung und eine als
„GReQL 2“ bezeichnete Java-Implementierung. Da im Rahmen der vorliegenden Arbeit die JavaKlassenbibliothek genutzt wird, ist mit „GReQL“ immer die Java-Version GReQL 2 gemeint.
Zentrale Komponenten von GReQL sind der Parser, der Auswerter, der Optimierer und eine
Funktionsbibliothek. Zudem existieren Container-Klassen für eine einheitliche Behandlung (der
Typen) von GReQL-Ergebnissen.
GReQL wurde gemäß [KAMP1996] bereits für die ersten Versionen des Graphenlabors entwickelt
und basiert auf der Graphen-Spezifikationssprache GRAL („Graph Specification Language“, siehe
[URL:GRAL]). Diese wurde ebenfalls von Ebert und Kollegen auf Grundlage der sogenannten „ZNotation“ (siehe [URL:Z]) entwickelt, einer Spezifikationssprache, die bereits seit 1977 an der
Universität Oxford ausgearbeitet wurde. Die Spezifikation von GReQL 2 erfolgte erstmalig in
[MAR2006], die Implementierung des Auswerters in [BILD2006] und die des Optimierers in
[HORN2008].
3.7.3.2
Einblick in die Syntax
Die Syntax von GReQL ist angelehnt an SQL. Dem GReQL-Auswerter wird eine Instanz des
abgefragten TGraphen sowie die GReQL-Abfrage als Zeichenkette übergeben. Als Ergebnis erhält
man die Instanz einer Containerklasse, die das Interface JValue implementiert. Einige dieser
Containerklassen implementieren zusätzlich Iterable und stellen Ergebnismengen dar. Welchen
Container-Typen man erhält, hängt von der Formulierung der Abfrage ab.
3.7.3.2.1
Ausdrücke
Es existieren eine Vielzahl möglicher Ausdrucksformen: arithmetische Ausdrücke,
Funktionsaufrufe, Schleifen, Attributzugriffe usw. Beispiele hierfür sind (je Zeile):
true
- 2
3 + 4
E // alle Kanten
33
V{ClassDefinition!}
from e:E with mutable == true reportSet e end // alle veraenderlichen
// Kanten als Set
Um Ausdrücke, die keine Abfrage an einen TGraphen darstellen, auswerten zu lassen, kann der
GReQL-Auswerter auch ohne TGraphen-Instanz zu einer Evaluierung veranlasst werden:
GreqlEvaluator eval = new GreqlEvaluator("3 + 4", null, null);
eval.startEvaluation();
int i = eval.getEvaluationResult().toInteger(); // Zuweisung von 7 an i
3.7.3.2.2
from-with-report-Ausdrücke
Für die Abfrage eines gegebenen TGraphen ist der from-with-report-Ausdruck die wichtigste
Ausdrucksform. Dabei besteht eine Abfrage gemäß [URL:GREQL] aus bis zu drei Teilen: einer
from-Klausel, einer with-Klausel und einer report-Klausel.
Zunächst werden in der from-Klausel Variablen und ihre Wertebereiche deklariert. Diese
Deklaration erfolgt in der Syntax
<Variablenname>:<Wertebereich>
nach dem from-Schlüsselwort. Innerhalb einer from-Klausel können mehrere solche Deklarationen
durch Kommata separiert werden. Der optionale zweite Abschnitt formuliert in einer with-Klausel
die Bedingungen auf diesen Variablen. Dabei sind insbesondere Pfadbeschreibungen in Form
regulärer Ausdrücke (regular path expressions, siehe [GREQLREF]) als Bedingungen relevant –
auf diese wird im folgenden Abschnitt näher eingegangen. Schließlich beschreibt eine reportKlausel die Ausgabe. Dabei existieren unterschiedliche Varianten des report-Schlüsselworts:
Neben report können u. a. auch reportSet oder reportMap verwendet werden. Durch diese
Wahl wird der Container-Typ des Ergebnisses beeinflusst.
3.7.3.2.3
Pfadbeschreibungen als reguläre Ausdrücke
Die Formulierung der Pfadbeschreibungen innerhalb des TGraphen erfolgt mittels regulärer
Ausdrücke. In diesen können neben gewöhnlichen Ausdrücken auch Kantensymbole und die
Kleene-Operatoren * und + verwendet werden. Als Kantensymbole werden dabei Pfeilausdrücke
der Art --> (ausgehende Kante), <-- (eingehende Kante), <-> (Kante ohne bestimmte Richtung)
oder --exp-> (genau eine Kante, die dem Ausdruck exp entspricht) verwendet. Die KleeneOperatoren werden auf eine solche Pfadbeschreibung angewandt und stehen wie bei regulären
Ausdrücken üblich für die beliebig häufige (bei + mindestens einmalige) Iteration des
beschriebenen Pfades. Ist eine fixe Anzahl von Iterationen gesucht, so kann diese über
<Pfadbeschreibung>^n ausgedrückt werden, wobei n für diese Anzahl steht. Für Start- und
Endknoten kann zudem eine Restriktion in Form von {<Knotentyp>} angegeben und mit einem
&-Symbol an die Pfadbeschreibung geknüpft werden: <Pfadbeschreibung> &
{<Knotentyp>} beschreibt alle Pfade, auf welche die <Pfadbeschreibung> zutrifft und deren
Endknoten vom Typ <Knotentyp> ist. Zu beachten ist, dass eine solche Pfadbeschreibung eine
34
Menge liefert und daher noch nicht zur Verwendung in einer with-Klausel taugt. Sie kann
allerdings verwendet werden, um den Wertebereich einer Variablen innerhalb der from-Klausel zu
definieren.
3.7.3.2.4
Beispiel einer Abfrage an den TGraphen
Im folgenden Beispiel werden alle Klassen-Definitionen gesucht, die ein Interface implementieren:
from c:V{ClassDefinition},
i:V{InterfaceDefinition},
t:E{IsTypeDefinitionOf}
with c <--* <-t-- i
reportSet c
end
Der reguläre Ausdruck c <--* <-t-- i bildet den Kern dieser Abfrage. Die Klassen-Definition
wird durch die Variable c vertreten. Zu dieser dürfen beliebig viele eingehende Kanten eines
beliebigen Typs führen. Dies wird durch <--* ausgedrückt. Durch den Teilausdruck <-t-- i wird
gefordert, dass irgendwo im Pfad eine IsTypeDefinitionOf-Kante vorkommt (repräsentiert
durch die Variable t), die von einer InterfaceDefinition (Variable i) weg zeigt. Dies kann nur
dann der Fall sein, wenn die Klassen-Definition entweder direkt oder über eine ihrer Superklassen
ein Interface implementiert.
An dieser Stelle sollte ein Einblick in die Formulierung von GReQL-Abfragen gegeben werden.
Die Sprache ist sehr viel umfangreicher als es an dieser Stelle erschöpfend dargestellt werden kann.
Auf umfassendere Dokumentationen wird im Kapitel 3.10. Weiterführende Informationen
hingewiesen.
3.8
3.8.1
Welche Vorteile können erhofft werden?
Lösung der Probleme anderer Ansätze
Im Kapitel 2.3. Bisherige Ansätze zur Repräsentation der Fakten wurden einige Alternativen zur
Verwendung von TGraphen für die Repräsentation von Java-Fakten beschrieben. Wie sich dort
unter 2.3.6. Zusammenfassung der Probleme der bisherigen Ansätze gezeigt hat, waren diese
Ansätze mit verschiedenen Problemen verbunden. TGraphen bieten Lösungen für eine Reihe dieser
Probleme an:
•
•
•
Abfrage von Information: Für die Abfrage von Information aus TGraphen steht die
Abfragesprache GReQL zur Verfügung, die über eine konsistente und erprobte Syntax
verfügt und sehr effizient arbeitet.
Anreicherung der Daten: Die Anreicherung des TGraphen ist problemlos möglich. Die
Erzeugung neuer Graphen-Elemente (Knoten oder Kanten) erfolgt auf einheitliche Weise
über Factory-Methoden. Handelt es sich um gänzlich neuartige Informationen, so ist ggf.
eine Anpassung des Metamodells des TGraphen erforderlich, die in einer hierfür
vorgesehenen Spezifikationsdatei erfolgt. Die vorliegende Arbeit liefert eine Reihe von
Beispielen für derartige Anreicherungen.
Einheitliche Datenstruktur: Die gesamte über den Java-Quelltext benötigte Information
35
•
3.8.2
wird in einer einheitlichen und konsistenten Datenstruktur vorgehalten. Die Elemente dieser
Datenstruktur sind entsprechend dem eingesetzten Metamodell typisiert und bieten somit
eine intuitiv verständliche Schnittstelle zum repräsentierten Quelltext an.
Performance: TGraphen gelten als laufzeit- und speichereffizient sowohl im Rahmen der
Faktenextraktion als auch bei Abfragen über die integrierte Abfragesprache GReQL (siehe
z. B. [KAHLE2006] S. 82ff). Eine Performance-Messung für einige Beispiel-Projekte wird
im Kapitel 7.3. Performance vorgestellt.
Einige formale Vorteile
Ebert und Kollegen nennen im Artikel „A Generic System to Support Multi-Level Understanding of
Heterogeneous Software“ aus [GUPRO1998] eine Reihe formaler Vorteile, von denen die
wichtigsten an dieser Stelle zusammengefasst werden:
•
•
•
3.8.3
Graphen als Standard-Formalismus: Graphen sind allgemein ein wohlverstandener
Formalismus, der mit effizienten Standard-Algorithmen ausgestattet ist.
Z-Notation als Grundlage: TGraphen liegt ein formal ausgearbeitetes Konzept zu Grunde,
welches auf einer Spezifikation in Z-Notation basiert (siehe auch 3.7.3.1. Bezeichnung,
Komponenten und Geschichte). Ebert und Kollegen betrachten dies als Grundlage für
konsistent entwickelte Werkzeuge, die auf dem Graphen-Repository arbeiten sollen.
Formale Spezifikation des Metamodells: Es liegt eine formale Spezifikation des
Metamodells für jede Graphen-Implementierung vor, welche die Implementierung von
darauf basierenden Refaktorisierungs-Werkzeugen unterstützt. Speziell für das Thema dieser
Arbeit bedeutet dies, dass jeder Entwickler von Refaktorisierungs-Werkzeugen für JavaQuelltext, die auf TGraphen arbeiten sollen, eine vollständige und als UMLKlassendiagramm exportierbare Dokumentation des Metamodells nutzen kann.
Eignung für programmanalytische Aufgaben
Die TGraphen des Graphenlabors wurden speziell konstruiert, um programmanalytische Aufgaben
damit bewältigen zu können. So sind TGraphen gemäß [EBERT1995] daraufhin angelegt, nicht nur
sprachsyntaktische, sondern auch zusätzliche Informationen über Software-Systeme aufnehmen zu
können. Dies unterscheidet TGraphen prinzipiell auch von abstrakten Syntaxbäumen (oder
-graphen), die historisch eine generische, insbesondere im Compilerbau genutzte Repräsentation der
bloßen Sprachsyntax darstellen. Die Ausrichtung auf analytische Einsatzzwecke zeigt sich auch bei
der aufwändig integrierten Abfragesprache GReQL. Diese bietet mächtige Analysemöglichkeiten,
die weit über die Programmierschnittstelle gewöhnlicher Datenstrukturen hinausgehen.
3.8.4
Programmiersprachenübergreifende Refaktorisierungen
[STEI2010] beschreibt auch die Möglichkeit programiersprachenübergreifender
Refaktorisierungen. TGraphen würden hierfür eine geeignete Grundlage liefern, da sie
grundsätzlich ebenfalls programmiersprachenübergreifend konstruiert sind. So bietet das
Repository-Konzept von TGraphen (siehe Grafik im Kapitel 3.5. Kurze Entwicklungsgeschichte)
bereits eine Infrastruktur zur Handhabung mehrerer Graphenrepräsentationen an.
36
In diesem Kontext könnte auch die Idee eines Referenz-Metaschemas interessant sein, die in
[WINTER2000] vorgestellt wird. So wäre es denkbar, verschiedene objektorientierte
Programmiersprachen in einem gemeinsamen Metaschema zu repräsentieren, von welchem die
programmiersprachenspezifischen Schemata abgeleitet werden. Dies würde es beispielsweise
ermöglichen, GReQL-Abfragen auf Typen des Metaschemas zu formulieren und somit AnalyseKomponenten zu entwickeln, die unabhängig von einer konkreten Sprache sind.
Die Möglichkeit zur Repräsentation von Information, die über die Syntax einer speziellen
Programmiersprache hinaus geht, würde auch die Repräsentation weiterer Nicht-Java-Dateien
ermöglichen, so z. B. Spezifikationsdateien von Dependency Injection Containern wie Spring,
EJB 3 oder JSF. Da in diesen Dateien u. a. auch Typ-Bezeichner und Paketpfade enthalten sind,
entsteht bei Refaktorisierungen, die zu Umbenennungen oder Verschiebungen führen, häufig ein
manueller Zusatzaufwand. Auf Basis von TGraphen könnten die Inhalte solcher
Spezifikationsdateien repräsentiert, abgefragt und im Rahmen einer Refaktorisierung modifiziert
werden.
3.9
Vorgehensweise bei Anpassungen im TGraphen
Die erweiterte Java-Faktenextraktion, die im Rahmen dieser Arbeit für TGraphen implementiert
wurde, lässt sich grob in Änderungen des Metamodells zur Build-Zeit und Änderungen am
generierten TGraphen zur Laufzeit unterteilen. Dieses Kapitel soll einen Überblick der wichtigsten
Entwicklungstätigkeiten gegeben, die im Rahmen dieser Modifikationen durchgeführt wurden, um
evtl. künftigen Autoren und Entwicklern, die auf der vorliegenden Arbeit aufbauen wollen, einen
schnellen Einstieg zu ermöglichen. Das folgende Diagramm gibt eine Übersicht des Workflows, der
im Rahmen der Entwicklungsarbeit beim Hinzufügen neuer Informationen durchlaufen wird.
37
Abbildung 8: Workflow bei der Umsetzung von TGraph-Anpassungen
Zu Beginn ist abzuwägen, ob die gewünschte neue Information adäquat durch das bestehende
Metamodell repräsentiert werden kann. Die im Metamodell verfügbaren Knoten- und Kantentypen
stellen die Entitäten dar, aus denen der Graph aufgebaut wird. Ist zu ergänzende Information
neuartig und kann durch bestehende Knoten- und Kantentypen oder Attribute nicht oder nur
umständlich abgebildet werden, so ist eine Anpassung des Metamodells in der Datei java5.tg
erforderlich (siehe 3.9.1. Anpassung des Metamodells). Nachdem dort die gewünschten
Anpassungen vorgenommen und ein Build des GraBaJa-Projekts durchgeführt wurden, sind die
Informationen zur Laufzeit in den generierten TGraphen aufzunehmen. Dies könnte durch eine
Anpassung des GraBaJa-Faktenextraktors geschehen. Da sich dieser jedoch als problematisch
erwiesen hat (siehe 7.2.2. Wartbarkeit des Faktenextraktors), wurden alle zur Laufzeit
vorzunehmenden Anpassungen im Rahmen dieser Arbeit nach erfolgter Faktenextraktion umgesetzt.
Innerhalb der GraBaJa-Ressourcen wurde für diese Arbeit ausschließlich das Metamodell, also die
Datei java5.tg angepasst. Die Anpassungen nach Faktenextraktion sind meist eine Abfolge von
38
Informationssuche im Graphen (siehe 3.9.2.1. Traversieren und Abfragen sowie 3.7.3. Die
Abfragesprache GReQL) und dem Erzeugen neuer Knoten und Kanten (siehe 3.9.2.3. Erzeugen von
Knoten und Kanten) oder dem Setzen von Attributen (siehe 3.9.2.2. Ändern von Attributwerten).
3.9.1
Anpassung des Metamodells
Durch das Metamodell wird festgelegt, welche Knoten- und Kantentypen im TGraphen verfügbar
sind und in welchen Beziehungen diese zueinander stehen. Eigenschaften und Beispiele des
GraBaJa-Metamodells wurden in Abschnitt 3.7.2. Metamodell beschrieben. Sollen neuartige
Informationen in den TGraphen aufgenommen werden, so ist hierfür in der Regel eine Anpassung
des Metamodells sinnvoll.
In [BALD2008] ist davon die Rede, dass die Modelländerungen in UML durchgeführt und per
Modelltransformation in die Schemadatei übertragen werden. Dies ist grundsätzlich auch mittels
des kommerziellen Werkzeugs „Rational Software Architect“ (RSA) möglich, über welches sich ein
Export in das XMI-Format erzeugen lässt, das wiederum in das Format der Spezifikationsdatei
konvertiert werden kann. Dieser Weg erscheint jedoch in Anbetracht des recht intuitiv
verständlichen Dateiformats als umständlich und ist zudem nicht allgemein zugänglich, da das
genannte Werkzeug kostenpflichtig lizenziert werden muss. Nur nebenbei sei bemerkt, dass auch
für die umgekehrte Transformation der Schemadatei in das XMI-Format des RSA im Graphenlabor
entsprechende Konvertierungsfunktionen zur Verfügung stehen.
Die GraBaJa-Schemadatei ist im GraBaJa-Entwicklungsprojekt unter dem Namen java5.tg
abgelegt. Nach der Modifikation dieser Datei ist für das GraBaJa-Projekt ein Ant-Build
auszuführen. Zu diesem Zweck wird das build-Target des Ant-Builds ausgeführt, der in der
build.xml-Datei des GraBaJa-Projekts spezifiziert ist. Der Build erzeugt eine neue grabaja.jarDatei, die in das eigene Entwicklungsprojekt zu integrieren ist.
Zwei Arten von Metamodell-Anpassungen werden im Folgenden ausführlicher beschrieben:
•
•
die Ergänzung von Attributen zu existierenden Typen
die Aufnahme neuer Knoten- und Kantentypen
3.9.1.1
Ergänzung von Attributen zu existierenden Typen
3.9.1.1.1
Typen für Attribute
Eine der formalen Eigenschaften von TGraphen besteht darin, dass alle Knoten und Kanten
attributiert sind (siehe 3.2. Formale Eigenschaften von TGraphen). Durch Attribute können Knoten
und Kanten auf einfache Weise um zusätzliche Informationen ergänzt werden.
Grundsätzlich kann jedes Attribut in der Schemadatei einen der folgenden Typen annehmen:
(1) Boolean, (2) Integer, (3) Long, (4) Double, (5) String, (6) Set<?> (wobei ? ein
beliebiger weiterer TGraph-Typ sein darf), (7) List<?>, (8) Map<?,?>, (9) definierte Enums und
(10) definierte Records.
39
Die Typen (1) bis (8) sind selbsterklärend. Zu beachten ist, dass es sich hier um TGraph-Typen
handelt und nicht um Java-Typen. Definierte Enums sind ihrerseits in der Schemadatei definiert. So
handelt es sich beispielsweise bei MethodInvocationTypes um ein solches Enum, das wie folgt
definiert ist:
EnumDomain MethodInvocationTypes( METHOD,
CONSTRUCTOR,
SUPERMETHOD,
SUPERCONSTRUCTOR,
EXPLICITCONSTRUCTOR ) ;
Mittels des Schlüsselworts EnumDomain können beliebige weitere Enum-Typen definiert werden,
mit denen dann Attribut-Deklarationen in der Schemadatei versehen werden können.
Analog zu EnumDomain können mittels RecordDomain Records in Analogie zu Pascal definiert
werden. Dieser Mechanismus wird aktuell nur selten genutzt und daher hier nicht ausführlicher
beschrieben.
3.9.1.1.2
Ergänzung der Attributliste eines Typs
Knoten- und Kantentypen sind in der Schemadatei als VertexClass bzw. EdgeClass definiert.
Am Beispiel des Knotentyps MethodInvocation soll die Ergänzung eines Attributs gezeigt
werden. MethodInvocation ist in der aktuellen Schemadatei wie folgt definiert:
VertexClass MethodInvocation : Expression { type : MethodInvocationTypes } ;
MethodInvocation verfügt bereits über ein Attribut namens type. Dieses ist seinerseits typisiert
durch den wie oben definierten Enum-Typ MethodInvocationTypes.
Eine Erweiterung der MethodInvocation um ein Boolean-Attribut sähe also wie folgt aus:
VertexClass MethodInvocation : Expression
{ implicit : Boolean, type : MethodInvocationTypes } ;
3.9.1.2
Ergänzung neuer Knoten- oder Kantentypen
Die Ergänzung neuer Knoten- oder Kantentypen erfolgt durch entsprechende VertexClass- oder
EdgeClass-Definitionen in der Schemadatei. Dabei kann ein Supertyp angegeben werden, dessen
Eigenschaften geerbt werden. Ein Beispiel für eine solche Supertyp-Beziehung zeigt die
Deklaration der MethodInvocation im vorhergehenden Abschnitt, die von Expression ableitet.
Dies ermöglicht beispielsweise die Nutzung von Supertypen in GReQL-Abfragen: Alle Ausdrücke
lassen sich durch eine einzige Suche nach dem Knotentyp Expression im TGraphen finden.
Nach einem Build des GraBaJa-Projekts sind die definierten Typen für den TGraphen verfügbar.
40
3.9.2
Modifikation des generierten TGraphen
Ein generierter GraBaJa-TGraph ist eine Instanz des Typs Java5 (auf die Angabe voll qualifizierter
Typen wird hier verzichtet, wenn die Bezeichner innerhalb des GraBaJa-Projekts eindeutig sind).
Um eine solche Instanz zu erzeugen, kann beispielsweise die GraphBuilder-Klasse verwendet
werden, welche entsprechende Methoden zum Parsen von Java-Dateien und zum Erzeugen des
TGraphen bereitstellt. Mittels der getGraph()-Methode kann vom GraphBuilder die erzeugte
Java5-Instanz bezogen werden. Diese dient als Ausgangspunkt für alle Operationen auf dem
erzeugten Graphen. In den folgenden Abschnitten werden die Basisoperationen beschrieben, die bei
der Entwicklungsarbeit mit TGraphen benötigt werden.
3.9.2.1
Traversieren und Abfragen
Im Abschnitt 3.7.3. Die Abfragesprache GReQL wurde bereits ausführlich auf GReQL
eingegangen. Das Graphenlabor bietet über GReQL hinaus verschiedene Möglichkeiten an, um
durch den TGraphen zu traversieren oder bestimmte Knoten und Kanten zu finden. In den
folgenden Abschnitten werden verschiedene Schnittstellen-Methoden beschrieben, die für die
Traversierung oder für das Absetzen von GReQL-Abfragen eingesetzt werden können.
Grundsätzlich gilt dabei, dass Methoden, die nicht zu einem Aufruf des GReQL-Auswerters führen,
effizienter arbeiten und daher bevorzugt werden sollten.
Die folgenden Beispiele sind z. T. leicht modifiziert aus [KAHLE2006] (S. 150ff) übernommen.
3.9.2.1.1
Traversieren aller Knoten des Graphen
Das Traversieren aller Knoten eines Graphen ist eine typische Operation und lässt sich auf Grund
der Ordnung des Graphen auf einfache Weise iterativ erledigen.
Vertex v1 = tgraph.getFirstVertex(); // tgraph ist eine Java5-Instanz
Vertex nextv = v1;
do {
// Hier tun, was immer mit dem Knoten zu tun ist.
nextv = nextv.getNextVertex();
} while (nextv != v1);
3.9.2.1.2
Traversieren aller Kanten des Graphen
Edge e1 = tgraph.getFirstEdge(); // tgraph ist eine Java5-Instanz
Edge nexte = e1;
do {
// Hier tun, was immer mit der Kante zu tun ist.
nexte = nexte.getNextEdge();
} while (nexte != e1);
41
3.9.2.1.3
Traversieren aller Inzidenzen zu einem Knoten
Edge nexti = vertex.getFirstIncidence();
while (nexti != null) {
// Hier tun, was immer mit der Kante zu tun ist.
// So gelangt man z. B. an das Objekt auf der anderen Seite der Kante:
Vertex thatvertex = nexti.getThat();
nexti = nexti.getNextIncidence();
}
Häufig möchte man nicht die Kanten selbst, sondern die damit verknüpften Objekte betrachten.
Hierfür kann die getThat()-Methode genutzt werden, die für alle Kantentypen zur Verfügung
steht. Gelangt man von einem bestimmten Knoten aus (z. B. wie oben mittels der SchnittstellenMethode getNextIncidence()) an eine Kante, so liefert getThat() immer den anderen mit der
Kante verknüpften Knoten. Mit getThis() erhält man den ursprünglichen Knoten zurück.
3.9.2.1.4
Suche nach Knoten bei fixem Pfad
Häufig sucht man zu einem Knoten einen oder eine Menge von weiteren Knoten, die über einen
bekannten Pfad mit dem Ausgangsknoten verknüpft sind. Zu diesem Zweck kann die
reachableVertices()-Methode auf jedem Knoten verwendet werden:
Set<ClassDefinition> cd_set =
methoddeclaration.reachableVertices(ClassDefinition.class,
new PathElement(IsMemberOf.class, EdgeDirection.OUT),
new PathElement(IsClassBlockOf.class, EdgeDirection.OUT));
In diesem Beispiel wird ausgehend von einer Methoden-Deklaration die enthaltende Klasse gesucht.
Gemäß Metamodell-Definition führt der Pfad zur enthaltenden Klasse über eine Kante des Typs
IsMemberOf zu einem Knoten des Typs Block und weiter über eine Kante des Typs
IsClassBlockOf zu der gesuchten ClassDefinition-Instanz. Um diesen Pfad zu spezifizieren
reicht es aus, die Kanten und ihre Richtung anzugeben. Die reachableVertices()-Methode gibt
also den gesuchten Typ und in der Folge eine beliebig lange Liste von PathElement-Instanzen,
jeweils bestehend aus Kantentyp und -richtung, zur Spezifikation des Suchpfades an. Als Ergebnis
erhält man immer ein Set des gesuchten Typs. Diese Suchmethode ist besonders effizient, da
hierfür nicht der GReQL-Auswerter benötigt wird. Sie erfordert andererseits auch sehr spezielle
Abfragen und eine detaillierte Kenntnis des Metamodells.
3.9.2.1.5
Suche nach Knoten von einem Knoten aus
Die reachableVertices()-Methode ist überladen und bietet auch eine Suche mittels GReQLAusdruck an:
String greql = " -->{^IsDeclarationOf,^IsTypeDefinitionOf}* & {Type}";
List<Type> typelist = v.reachableVertices(greql, Type.class);
42
Die Abfrage im obigen Beispiel sucht für einen beliebigen gegebenen Knoten v nach dem
enthaltenden Typ. Dies unterscheidet diese Version der reachableVertices()-Methode
wesentlich von der oben beschriebenen: Die Pfadspezifikation ist nicht fix, sondern ein regulärer
Ausdruck, der es ermöglicht, unterschiedliche Pfade als mögliche Treffer zu spezifizieren.
Demgemäß ist die Ausführung dieser Methode jedoch auch weniger performant als die Version mit
fixer Pfadangabe.
3.9.2.1.6
Suche nach Knoten auf Graph-Ebene mittels GReQL
Wie bereits im Abschnitt 3.7.3. Die Abfragesprache GReQL beschrieben, können Abfragen auch auf
Graph-Ebene erfolgen. Hierzu wird eine Instanz der Klasse GreqlEvaluator erzeugt, die
Auswertung der übergebenen Abfrage veranlasst und anschließend das Ergebnis ausgelesen. Ein
Beispiel für diese Vorgehensweise wurde bereits im o. g. Kapitel gegeben.
3.9.2.1.7
Suche nach Kanten
Für die Suche nach Kanten stehen weniger Möglichkeiten zur Verfügung als für die Suche nach
Knoten. So existieren insbesondere keine zu reachableVertices() analoge Methoden für die
Suche nach Kanten. Auf Graph-Ebene kann allerdings mittels der Klasse GreqlEvaluator auch
nach Kanten gesucht werden. So findet die nachfolgende Suche alle IsMemberOf-Kanten im
Graphen (man beachte, dass vor den geschweiften Klammern für die Suche nach Kanten ein E
anzugeben ist):
GreqlEvaluator eval = new GreqlEvaluator(
"from x:E{IsMemberOf}" +
" reportSet x" +
" end", tgraph, null);
3.9.2.2
Ändern von Attributwerten
Für jedes Attribut eines Typen werden beim Ausführen des GraBaJa-Builds Getter und Setter
erzeugt, über die auf die Attribute zugegriffen werden kann. Das Ändern von Attributwerten ist
entsprechend einfach über den Setter des Attributs möglich:
methodinvocation.set_type(MethodInvocationTypes.SUPERCONSTRUCTOR);
Im gegebenen Beispiel wird das type-Attribut einer MethodInvocation-Instanz auf den Wert
SUPERCONSTRUCTOR des Enums MethodInvocationTypes gesetzt.
3.9.2.3
Erzeugen von Knoten und Kanten
Für das Erzeugen von Knoten und Kanten sollten die entsprechenden Factory-Methoden des
Graphen genutzt werden, da im Rahmen der Erzeugung auch einige Verwaltungsinformationen im
Graphen korrekt gesetzt werden müssen. Für jeden Knoten- oder Kantentyp stehen entsprechende
create*()-Methoden zur Verfügung. So erfolgt die Erzeugung einer neuen ClassDefinitionInstanz wie folgt:
43
ClassDefinition cd = tgraph.createClassDefinition();
Die so erzeugte Instanz wäre nun zwar im Graphen verfügbar und könnte auch über die
getNextVertex()-Methode grundsätzlich iterativ erreicht werden, ohne zusätzliche
Informationen und Verknüpfungen wäre die erzeugte ClassDefinition jedoch kaum sinnvoll.
Zunächst sind die Attribute der Klasse entsprechend zu setzen:
cd.set_external(false);
cd.set_fullyQualifiedName("mypackage.MyClass");
cd.set_name("MyClass");
Da durch eine Klasse auch immer ein Typ definiert wird, sollte zudem eine entsprechende Instanz
des Typs QualifiedType miterzeugt und verknüpft werden:
QualifiedType qt = tgraph.createQualifiedType();
qt.set_fullyQualifiedName("mypackage.MyClass");
Diese ist nun mittels einer Kante des Typs IsTypeDefinitionOf mit der ClassDefinition zu
verknüpfen:
tgraph.createIsTypeDefinitionOf(cd, qt);
Die Integration dieser Knoten in den TGraphen kann nun je nach Bedarf noch weiter fortgesetzt
werden. Insbesondere wäre es hier sinnvoll, die entsprechenden Instanzen und Verknüpfungen zur
Quelltext-Datei mittels der entsprechenden SourceFile und TranslationUnit-Knoten
herzustellen und so den bisher isolierten Teilgraphen im Gesamtgraphen vom Program-Knoten aus
erreichbar zu machen. Das Beispiel zeigt, dass Ergänzungen im TGraphen in der Regel ganze
Bündel von Knoten und Kanten betreffen, da die einzelnen Knoten und Kantentypen so feingranular
sind, dass sie für sich genommen keinen Sinn machen.
3.9.2.4
Löschen von Knoten und Kanten
Das Löschen von Knoten oder Kanten ist entweder über eine Graphen-Methode oder über die
delete()-Methode jedes einzelnen Typen möglich:
v1.delete();
tgraph.deleteVertex(v2);
e1.delete();
tgraph.deleteEdge(e2);
//
//
//
//
Loescht den Knoten.
Gleichwertig.
Loescht die Kante.
Gleichwertig.
3.10
Weiterführende Informationen
3.10.1
Weblinks und Dokumentation
Die Arbeitsgruppe Ebert des IST der Universität Koblenz-Landau pflegt im Bereich ihres
Webauftritts ein Einstiegsportal zum Thema Graphentechnologie unter [URL:ISTGRAPHEN], von
dem aus speziellere Übersichtsseiten zu den Themen TGraphen, Graphenlabor, GReQL, GUPRO
sowie damit verbundenen Themen erreicht werden können.
44
Über diesen offiziellen Webauftritt hinaus pflegt das IST ein Wiki unter [URL:ISTWIKI]. Dort
werden Artikel und Tutorials zu JGraLab und GReQL angeboten, die einen praxisorientierten
Einblick gewähren. Spezielle Wiki-Artikel zu GraBaJa fehlen allerdings.
Bei Problemen mit der Nutzung der vom IST bereitgestellten Frameworks kann das BugtrackingSystem [URL:HELENA] der Universität Koblenz-Landau verwendet werden.
Eine Einführung in die grundlegenden Konzepte und Komponenten von GUPRO gibt das
umfassende Werk [GUPRO1998], das wesentliche Forschungsarbeiten und Handbücher bis zum
Jahr 1998 zusammenstellt. Die Grundlagen-Informationen zu JGraLab, GraBaJa und GReQL 2 sind
lediglich über die bereits angeführten Diplom- und Studienarbeiten einsehbar.
Für GReQL existiert die hilfreiche Referenzkarte [GREQLREF], die zwar nicht öffentlich
bereitgestellt wird, aber jederzeit bei der Arbeitsgruppe Ebert angefragt werden kann.
3.10.2
Download und Lizenzinformation
Die jeweils aktuellen Entwicklungsprojekte, die für die Verwendung von JGraLab und GraBaJa
notwendig sind, können über ein Quellcode-Repository des IST heruntergeladen werden. Auf den
Wikiseiten des IST ist zudem unter [URL:JGRALABDOWN] eine Download-Seite verfügbar, über
die Binaries von JGraLab bezogen werden können. Eine analoge Download-Seite für GraBaJa
existiert allerdings nicht. Die Version der Projekte, die der vorliegenden Arbeit zu Grunde lag,
wurde zudem in ein Repository des Lehrgebiets Steimann der Fernuniversität Hagen aufgenommen.
Für den Zugang zu diesem Repository ist ein Nutzerzugang erforderlich, der im Lehrgebiet erbeten
werden kann.
JGraLab wird gemäß Wikiseiten unter General Public License (GPL) in Version 3 (siehe
[URL:GPL3]) veröffentlicht. In den Datei-Headern der JGraLab-Quelltexte wird zudem eine
Ausnahme formuliert, welche die Lizenzerklärung kompatibel zur Eclipse Public License (EPL,
siehe [URL:EPL]) macht, um beispielsweise das Entwickeln und Veröffentlichen von EclipsePlugins unter EPL zu ermöglichen, was bei reiner GPL-Lizensierung auf Grund der Restriktionen
der GPL nicht möglich wäre. Dieselben Lizenzbedingungen gelten gemäß den Datei-Headern der
Quellcode-Dateien für GraBaJa.
Zu beachten sind zudem eine Reihe weiterer Open Source-Lizenzen, die für Komponenten wie
ANTLR, Apache Commons und weitere Utilities benötigt werden.
Sollen JGraLab und / oder GraBaJa kommerziell weiterentwickelt und ggf. in Produkte integriert
werden, die nicht unter GPL oder EPL veröffentlicht werden sollen, so ist eine individuelle
Lizenzierung für kommerzielle Zwecke möglich. Hierfür ist Kontakt mit dem IST aufzunehmen.
3.10.3
Referenzprojekte
Um einen Einblick in Verwendung und Reifegrad des Graphenlabors zu geben, werden in den
folgenden Abschnitten kurze Beschreibungen von Referenzprojekten gegeben. Die Projektliste ist
nicht vollständig. In den Projekten wird jeweils JGraLab eingesetzt. GraBaJa wurde lediglich für
ein Seitenthema im Rahmen einer Masterarbeit im Kontext des SOAMIG-Projekts eingesetzt (siehe
3.10.3.2. SOAMIG). Ein offizielles Referenzprojekt, das GraBaJa zur Repräsentation von JavaFakten einsetzt, existiert bislang nicht.
45
3.10.3.1
ReDSeeDS
ReDSeeDS („Requirements-Driven Software Development System“, siehe [URL:REDSEEDS]) ist
ein von über zehn Forschungseinrichtungen und kommerziellen Unternehmen seit 2006 betriebenes
Projekt, welches u. a. durch die Europäische Union unterstützt wird. Auf der Grundlage einer
Spezifikationssprache für Anforderungen (Requirements Specification Language, RSL) wurde eine
Engine entwickelt, welche das Formulieren von Anforderungen, ihre Verwaltung in einem FaktenRepository, das Stellen von Abfragen an das Repository, das Extrahieren wiederverwendbarer
Anforderungs-Elemente sowie eine Transformation in Design-Modelle unterstützen soll. Innerhalb
dieser Engine wird JGraLab als Fakten-Repository eingesetzt. Das Erfragen von Informationen aus
diesem Repository erfolgt über GReQL.
3.10.3.2
SOAMIG
SOAMIG ist gemäß [URL:SOAMIG] ein vom Bundesministerium für Bildung und Forschung
gefördertes Projekt mit dem Ziel der „Entwicklung eines allgemeinen Vorgehensmodells für die
Softwaremigration durch Transformation von Legacy-Systemen in serviceorientierte Architekturen
und die Unterstützung der Transformation durch Softwarewerkzeuge“. Auch im SOAMIG-Projekt
kommt JGraLab als Fakten-Repository zum Einsatz, hier u. a. gemäß [ZIM2009] prototypisch für
COBOL-Fakten, die um Geschäftsprozessmodelle und Tracability-Informationen erweitert werden.
Auch eine weitere (von GraBaJa unabhängige) Variante von Java-Graphen wurde im SOAMIG
entwickelt und eingesetzt. Im Rahmen der Masterarbeit [FUHR2011] wurde darüber hinaus
GraBaJa für das Einlesen von Java-Quellcode eingesetzt. Die eingelesenen Javagraphen wurden
dort mit einem Graphen zur Repräsentation von Aktivitätsdiagrammen verschmolzen und mittels
GReQL analysiert. Eine gute Übersicht der in SOAMIG entwickelten Werkzeuge und der
Integration des Graphenlabors gibt [HORN2011].
3.10.3.3
COBUS
Das Projekt COBUS (COBOL-Bestandsanalyse und -Sanierung) wird vom IST seit November 2009
in Zusammenarbeit mit der Debeka Versicherungsgruppe durchgeführt. Ziel ist es gemäß
[URL:COBUS], „eine umfassende Bestandsanalyse und -bewertung des COBOL-basierten
Softwaresystems der Debeka durchzuführen. Auf Grundlage dieser Analyse und Bewertung werden
anschließend geeignete Maßnahmen abgeleitet, mit denen die Qualität des Systems optimiert und
die langfristige Weiterentwickelbarkeit sichergestellt werden kann“. Für die Analyse der
umfangreichen COBOL-Fakten wird in diesem Projekt JGraLab eingesetzt. Hierfür werden die
COBOL-Quelltexte in ein vereinfachtes COBOL-Schema überführt, für welches dann mittels
GReQL diverse Metriken berechnet und Code-Konventionen überprüft werden.
46
4
Identifikation der notwendigen Anpassungen
In diesem Kapitel wird zunächst erläutert, warum es überhaupt erforderlich ist, am TGraphen, wie
er vom Faktenextraktor des GraBaJa-Projekts für einen gegebenen Java-Quelltext erzeugt wird,
Anpassungen vorzunehmen. Die weiteren Abschnitte gleichen die im originalen TGraphen
repräsentierten Informationen mit denen ab, die im Kapitel 2.2. Benötigte Java-Fakten für
Sichtbarkeitsconstraints gefordert wurden, und beschreiben die notwendigen Erweiterungen des
TGraphen zur Repräsentation dieser Fakten.
4.1
Motivation
Warum sind überhaupt Anpassungen am TGraphen vorzunehmen? Wie in [BALD2008] gezeigt
wurde, sind analog zum Eclipse-AST alle syntaktischen Fakten der eingelesenen Quelltexte
vollständig im TGraphen repräsentiert. Für den Entwickler einer Refaktorisierung sind damit auch
alle Fakten, die im Abschnitt 2.2.2. Eigenschaften von Deklarationen und Referenzen beschrieben
wurden, programmatisch abfragbar bzw. mit der entsprechenden Kenntnis von Java-Interna aus den
gegebenen Informationen abzuleiten. Dennoch sprechen folgende Gründe für eine erweiterte
Faktenextraktion außerhalb eines konkreten Refaktorisierungs-Werkzeugs:
•
•
•
•
4.2
Allgemeine Nützlichkeit: Die angereicherten Informationen sind für Analysezwecke
allgemein nützlich und keineswegs nur für Sichtbarkeitsconstraints relevant. Es wäre daher
nicht angemessen, innerhalb des Constraint-Generators den nicht unerheblichen
Implementierungsaufwand zu betreiben, der für die Extraktion dieser Fakten notwendig ist.
Performance: Im Rahmen der Constraint-Generierung wird so häufig auf diese
Informationen zugegriffen, dass es schon aus Performance-Gründen geboten erscheint, sie in
einer effizient abfragbaren Form in den TGraphen aufzunehmen.
Änderungen des Metamodells: Änderungen des Metamodells des TGraphen sind
grundsätzlich in einem separaten Entwicklungsprojekt nicht möglich, sondern werden im
Rahmen eines GraBaJa-Builds auf der Basis einer Spezifikationsdatei per Codegenerator
erzeugt. Ohne Änderung des Metamodells hätten einige Informationen gar nicht oder nur
schwer in den TGraphen aufgenommen werden können.
Evaluation der Arbeit mit TGraphen: Neben dem konkreten Ziel der Anreicherung des
TGraphen um die für die Generierung von Sichtbarkeitsconstraints notwendigen
Informationen ist es auch Ziel dieser Arbeit zu untersuchen, inwieweit sich TGraphen
generell für die Anreicherung und Abfrage von Zusatzinformationen eignen. Vision des
constraintbasierten Refaktorisierungsansatzes ist es gemäß [STEI2010] letztlich,
verschiedenen Refaktorisierungs-Werkzeugen eine gemeinsame Infrastruktur
bereitzustellen, aus welcher sich diese nach Baukastenprinzip bedienen können. Wenn ein
TGraph die Kernkomponente einer solchen gemeinsamen Infrastruktur sein soll, dann ist zu
fragen, ob sich die Entwicklungsarbeit mit TGraphen nach einer vernünftigen
Einarbeitungszeit als praktikabel erweist.
Abgleich der Eigenschaften von Deklarationen und Referenzen
Die folgende Tabelle zeigt auf, welche der für Deklarationen und Referenzen geforderten
Eigenschaften (siehe Abschnitt 2.2.2. Eigenschaften von Deklarationen und Referenzen) in den
TGraphen aufzunehmen sind.
47
Eigenschaften von Deklarationen
Eigenschaft
Repräsentation im unmodifizierten Anpassungsbedarf und Verweis
GraBaJa-TGraphen
auf detaillierte Beschreibung
Access-Modifier
Access-Modifier sind grundsätzlich
im TGraphen mittels des Knotentyps
Modifier repräsentiert und werden
dem jeweiligen Deklarations-Element
über eine IsModifierOf*-Kante
zugeordnet. Der Typ des Modifiers
wird dabei über das Attribut type der
Modifier-Instanz repräsentiert.
Nicht repräsentiert sind allerdings
implizite Modifier, da der
Faktenextraktor grundsätzlich nur
explizite Syntaxinformation für
die Erzeugung heranzieht. Die
notwendigen Erweiterungen um
implizite Java-Elemente werden
im Kapitel 4.3. Repräsentation
impliziter Quelltext-Elemente
beschrieben.
static-Modifier für
Die Repräsentation erfolgt im
TGraphen analog zu AccessModifiern.
Die Notwendigkeit zur
Anreicherung des impliziten
static-Modifiers für InterfaceMember wird im Abschnitt 4.3.5.
Implizite Modifier im Kontext
von Interfaces beschrieben.
Die Repräsentation erfolgt im
TGraphen analog zu AccessModifiern.
Die Notwendigkeit zur
Anreicherung des impliziten
abstract-Modifiers für
Interface-Member wird im
Abschnitt 4.3.5. Implizite
Modifier im Kontext von
Interfaces beschrieben.
Identifier
Identifier sind für alle DeklarationsArten durch Knoten des Typs
Identifier im TGraphen
repräsentiert. Die Zuordnung erfolgt
über unterschiedliche Kantentypen,
die von der Deklarations-Art
abhängen, so z. B. IsClassNameOf
für Klassen-Deklarationen oder
IsNameOfMethod für MethodenDeklarationen.
Kein Anpassungsbedarf.
Typen
Die im Rahmen von Deklarationen
Kein Anpassungsbedarf.
verwendeten Typen sind im TGraphen
durch Knoten der Typen
BuiltInType (für primitive
Datentypen) und QualifiedType
(für Referenz-Typen) repräsentiert.
Die Verknüpfung des jeweiligen
Deklarations-Elements mit diesen
Knoten erfolgt je nach DeklarationsArt über Kanten der Typen
VariablenDeklarationen
abstract-Modifier
für MethodenDeklarationen
48
Eigenschaften von Deklarationen
IsTypeOf, IsTypeOfParameter,
IsExceptionThrownBy und
IsUpperBoundOfTypeParameter.
Lokationen
Die Lokationen von DeklarationsElementen werden im TGraphen über
das Attribut fullyQualifiedName
der jeweiligen Typ-Definition, in der
die Deklaration enthalten ist,
repräsentiert. Für jedes DeklarationsElement kann die enthaltende TypDefinition über eine einzige GReQLAbfrage ermittelt und somit die
Lokation aus dem genannten Attribut
ausgelesen werden.
Formale
Parameterliste
Die formale Parameterliste von
Methoden und Konstruktoren wird
über Instanzen des Typs
Wie sich herausgestellt hat, wird
der Wert des
fullyQualifiedNameAttributs vom Faktenextraktor
nicht immer korrekt ermittelt.
Details zu diesem Problem
werden im Kapitel
4.7. Fehlerhafte
Lokationsinformation für Klassen
beschrieben.
ParameterDeclaration
repräsentiert, die mittels der
Kantentypen
IsParameterOfMethod oder
IsParameterOfConstructor mit
der jeweiligen Deklaration verknüpft
sind. Die Reihenfolge der Parameter
wird über die Inzidenzordnung des
TGraphen verwaltet, d. h. beim
Iterieren über die IsParameterOf*Kanten (mittels der
getIsParameterOf
Incidences()-Methode der
Deklaration) durchläuft man die
Parameter in der gewünschten
Reihenfolge.
SupertypBeziehungen
Supertyp-Beziehungen werden je nach
Art der beiden verknüpften Typen
über unterschiedliche Kantentypen
repräsentiert. Für SuperklassenBeziehungen existiert der Kantentyp
IsSuperTypeOfClass. Ist ein
Interface der Supertyp (per
implements-Schlüsselwort), so
existieren verschiedene
IsInterfaceOf*-Kantentypen wie
z. B. IsInterfaceOfClass.
Erweitert ein Interface ein anderes
(mittels extends-Schlüsselwort), so
49
Der Faktenextraktor ignoriert die
implizite Supertyp-Beziehung zu
Object, wenn ein Typ keine
explizite extends-Beziehung
deklariert. Ebenso wenig wird die
implizite Supertyp-Beziehung
einer Enum-Definition zu Enum
repräsentiert. Details zu diesen
impliziten Erweiterungen werden
in den Abschnitten
4.3.3. Implizite Erweiterung von
Object und 4.3.4. Implizite
Erweiterung von Enum
Eigenschaften von Deklarationen
heißt der Kantentyp
überraschenderweise
beschrieben.
IsSuperClassOfInterface.
Spezielle Supertyp-Beziehungen
ergeben sich gemäß [JLS3] § 4.10.3
für Array-Typen. Insbesondere endet
die Typ-Hierarchie von Array-Typen
bei Object, so dass alle Array-Typen
die Methoden von Object erben. Im
Hinblick auf die
Sichtbarkeitsconstraints sind
demgemäß mögliche OverrideBeziehungen und statische Bindungen
bzgl. dieser Methoden zu überprüfen.
Override-Beziehungen scheiden aus,
da ein Array-Typ keine eigenen
Methoden deklarieren kann. Die
statische Bindung des Aufrufs einer
Object-Methode auf einem Array
könnte für die Contraint-Regeln Acc1, Acc-2, Inh-1, Inh-2 und Ovr
relevant sein. All diese Regeln sind
jedoch entweder für die Beziehung
eines Array-Typs zu Object immer
erfüllt (Acc-1, Inh-1), oder die
durch diese Regeln beschriebene
Konstellation kann für Array-Typen
nicht auftreten (Acc-2, Inh-2, Ovr).
Override-Beziehung
Die Override-Beziehung wird im
TGraphen nicht repräsentiert.
Die Override-Beziehung ist in
den TGraphen aufzunehmen.
Details werden im Kapitel
4.4. Repräsentation der OverrideBeziehung beschrieben.
Deklarations-Art
Alle Deklarations-Arten werden im
TGraphen über entsprechende
Knotentypen repräsentiert:
ClassDefinition,
InterfaceDefinition,
EnumDefinition,
AnnotationDefinition,
MethodDeclaration,
MethodDefinition,
ConstructorDefinition,
VariableDeclaration,
Kein Anpassungsbedarf.
AnnotationField,
50
Eigenschaften von Deklarationen
TypeParameterDeclaration.
Menge der TypParameter
Die Menge der Typ-Parameter wird
über Instanzen des Typs
Kein Anpassungsbedarf.
TypeParameterDeclaration
repräsentiert. Die Verknüpfung zu dem
jeweiligen mit Typ-Parameter
versehenen Deklarations-Element
(Klasse, Interface, Konstruktor,
Methoden-Deklaration) erfolgt mittels
des Kantentyps
IsTypeParameterOf.
Werden alle
Deklarationen
repräsentiert?
Explizite Deklarationen werden
vollständig repräsentiert.
Der Faktenextraktor ignoriert
implizite KonstruktorDefinitionen in Klassen, die nicht
über eine explizite KonstruktorDefinition verfügen. Dieser
Mangel wird im Abschnitt
4.3.2. Implizite KonstruktorDefinitionen behandelt.
Tabelle 3: Abgleich der Eigenschaften von Deklarationen
Eigenschaften von Referenzen
Eigenschaft
Repräsentation im unmodifizierten Anpassungsbedarf und Verweis
GraBaJa-TGraphen
auf detaillierte Beschreibung
Identifier
Für die Identifier von Referenzen wird Kein Anpassungsbedarf.
ebenfalls der Knotentyp Identifier
herangezogen. Die Kantentypen
unterscheiden sich je nach Art der
Referenz:
IsNameOfInvokedMethod für
Methoden- und Konstruktor-Aufrufe
sowie IsFieldNameOf für VariablenZugriffe.
Lokationen
Die Lokationen von Referenzen
können wie die für Deklarationen über
das fullyQualifiedName-Attribut
der enthaltenden Typ-Definition
ermittelt werden.
Der Anpassungsbedarf wurde
bereits für die LokationsEigenschaft von Deklarationen
beschrieben.
Statische Bindungen
Die Repräsentation statischer
Bindungen erfolgt über die
Kantentypen
Es ist eine Neu-Implementierung
der statischen Bindungen
erforderlich. Eine ausführliche
Untersuchung und Begründung
erfolgt im Kapitel
IsDeclarationOfInvoked
Method sowie
51
Eigenschaften von Referenzen
IsDeclarationOfAccessed
Field.
Typ-Referenzen
4.5. Repräsentation statischer
Bindungen.
Alle im Abschnitt 2.2.2. Eigenschaften Kein Anpassungsbedarf.
von Deklarationen und Referenzen
beschriebenen Typ-Referenzen sind im
TGraphen repräsentiert. Hierzu
existieren zahlreiche unterschiedliche
Kantentypen, die hier nicht vollständig
wiedergegeben werden sollen. Die
folgenden Beispiele sollen einen
Eindruck von der Repräsentation der
Typ-Referenzen im Metamodell
vermitteln:
IsReturnTypeOf,
IsTypeOfVariable,
IsInterfaceOf,
IsSuperClassOf,
IsElementTypeOf,
IsCastedTypeOf,
IsExceptionThrownBy
Empfänger-Typen
Beziehungen von Methoden- und
Konstruktor-Aufrufen sowie FeldZugriffen zum jeweiligen EmpfängerTypen werden nicht im TGraphen
repräsentiert.
Die Referenz auf EmpfängerTypen ist im TGraphen
aufzunehmen. Details werden im
Kapitel 4.6. Referenzierung von
Empfänger-Typen beschrieben.
Werden alle
Referenzen
repräsentiert?
Zwei Arten von Referenzen werden
nicht repräsentiert. Zum einen erzeugt
der Compiler für jeden Konstruktor
einer Klasse die implizite InstanzInitialisierungs-Methode (engl.
instance initialization method)
<init> mit dem Rückgabetyp void
und der Parameter-Liste des
Konstruktors. Innerhalb dieser
Methode werden allen MemberVariablen der Klasse Default-Werte
gemäß [JLS3] § 4.12.5 zugewiesen.
Bei diesen Zuweisungen handelt es
sich letztlich um Referenzen auf die
jeweiligen Variablen-Deklarationen,
die im TGraphen jedoch nicht
repräsentiert sind. Hieraus ergeben
sich für die Generierung der
Sichtbarkeitsconstraints jedoch keine
Probleme, da die fehlenden impliziten
Der zweite fehlende ReferenzTyp ist der implizite super()Aufruf zu Beginn eines jeden
Konstruktor-Blocks, wenn ein
expliziter super()-Aufruf fehlt.
Dieses Problem wird näher im
Abschnitt 4.3.1. Implizite
super()-Aufrufe behandelt.
52
Eigenschaften von Referenzen
Zuweisungen die statischen
Eigenschaften des Programms nicht
verändern.
Tabelle 4: Abgleich der Eigenschaften von Referenzen
4.3
Repräsentation impliziter Quelltext-Elemente
Im Rahmen der Untersuchungen für diese Arbeit wurden eine Reihe impliziter Quelltext-Elemente
identifiziert, welche für die Generierung von Sichtbarkeitsconstraints relevant sind. Diese Relevanz
wird im Folgenden für die einzelnen Elemente näher erläutert.
4.3.1
Implizite super()-Aufrufe
Spracheigenschaft
Gemäß [JLS3] § 8.8.7 wird beim Fehlen
eines expliziten Konstruktor-Aufrufs zu
Beginn eines Konstruktor-Blocks vom
Compiler implizit ein Aufruf des
parameterlosen Konstruktors
angenommen.
Relevanz für Refaktorisierungen
Wenn eine Subklasse einen super()-Aufruf tätigt, so
muss ihre Superklasse einen für die Subklasse
sichtbaren parameterlosen Konstruktor definieren.
Wäre der parameterlose Konstruktor der Superklasse
für die Subklasse nicht sichtbar, weil er z. B. durch
eine Refaktorisierung auf private gesetzt wird, so
resultierte dies in einem Syntax-Fehler.
Relevante Contraint-Regeln: Acc-1.
Tabelle 5: Implizite super()-Aufrufe
4.3.2
Implizite Konstruktor-Definitionen
Spracheigenschaft
Gemäß [JLS3] § 8.8.9 wird für Klassen
ohne Konstruktor-Definition automatisch
ein Default-Konstruktor ohne Parameter
und ohne throws-Klausel bereitgestellt,
welcher den parameterlosen Konstruktor
der Superklasse aufruft.
Relevanz für Refaktorisierungen
Da es durch die implizite Konstruktor-Definition zu
einem super()-Aufruf kommt, gilt das im
vorhergehenden Absatz Gesagte.
Die Vorgehensweise bei der Anreicherung des
TGraphen um implizite Konstruktor-Definitionen wird
ausführlich im Kapitel 5.1. Beispiel für eine
Anreicherung beschrieben.
Relevante Constraint-Regeln: Acc-1.
Tabelle 6: Implizite Konstruktor-Definitionen
53
4.3.3
Implizite Erweiterung von Object
Spracheigenschaft
Relevanz für Refaktorisierungen
Gemäß [JLS3] § 4.3.2 ist die Klasse
Object Superklasse aller anderen
Klassen. Daraus folgt, dass eine Klasse,
die nicht explizit von einer anderen
Klasse ableitet, implizit direkte Subklasse
von Object ist.
Keine der einer Superklasse (hier Object) geerbten
Methoden darf in der Subklasse eine geringere
Sichtbarkeit haben darf als in der Superklasse selbst.
Relevante Constraint-Regeln: Sub-1, Dyn-1.
Tabelle 7: Implizite Erweiterung von Object
4.3.4
Implizite Erweiterung von Enum
Spracheigenschaft
Relevanz für Refaktorisierungen
Gemäß [JLS3] § 8.9 leiten alle EnumDie von Enum geerbten Methoden können in EnumDefinitionen implizit von der Superklasse Definitionen überschrieben werden. Wie bereits im
Enum ab.
vorhergehenden Abschnitt festgestellt wurde, darf eine
überschreibende Methode nicht weniger sichtbar sein
als die überschriebene.
Relevante Constraint-Regeln: Sub-1, Dyn-1.
Tabelle 8: Implizite Erweiterung von Enum
4.3.5
Implizite Modifier im Kontext von Interfaces
Im Kontext von Interfaces spielen zahlreiche implizite Modifier eine Rolle.
Kurzbeschreibung
abstract-Modifier für das
Interface selbst
Spracheigenschaft
Gemäß [JLS3] § 9.1.1.1 ist
jedes Interface implizit
abstract.
public-Modifier für Interface- Gemäß [JLS3] § 9.1.5 sind alle
Member
Member eines Interfaces
implizit public. Zu den
Membern eines Interfaces
zählen die Deklarationen von
Methoden, Variablen, Klassen,
Interfaces (und damit auch
Annotationstypen, da diese
gemäß [JLS3] § 9.6 spezielle
54
Relevanz für
Refaktorisierungen
Nicht relevant für
Sichtbarkeitsconstraints.
Die Relevanz ist in diesem Fall
offensichtlich, da die Vergabe
anderer Sichtbarkeiten als
public syntaktisch verboten
ist. Des Weiteren dürfen
implementierende Klassen
diese Sichtbarkeit nicht
reduzieren.
Kurzbeschreibung
Spracheigenschaft
Interface-Definitionen sind)
sowie Enums.
static-Modifier für Interface- Gemäß [JLS3] § 9.3 sind alle
Variablen-Deklarationen im
Body eines Interfaces implizit
static.
Member
Gemäß [JLS3] § 9.5 sind
weiterhin alle Member-TypDeklarationen (engl. member
type declarations) implizit
static. Member-Typen sind
Klassen, Interfaces (und damit
auch Annotationstypen) und
Enums.
abstract-Modifier für
Interface-Methoden
final-Modifier für Variablen-
Deklarationen in Interfaces
Relevanz für
Refaktorisierungen
Relevante Constraint-Regeln:
Acc-1, Sub-1, Dyn-1, Abs.
Relevante Constraint-Regeln:
Inh-2. Auf Grund der
Komplexität dieser Regel wird
hier auf eine Wiederholung der
Problemstellung verzichtet.
Gemäß [JLS3] § 9.4 ist jede
Relevante Constraint-Regel:
Methoden-Deklaration im Body Abs.
eines Interfaces implizit
abstract.
Gemäß [JLS3] § 9.3 ist jede
Nicht relevant für
Variablen-Deklaration im Body Sichtbarkeitsconstraints.
eines Interfaces implizit final.
Tabelle 9: Implizite Modifier im Kontext von Interfaces
4.3.6
Impliziter Modifier für Annotationstyp-Elemente
Spracheigenschaft
Relevanz für Refaktorisierungen
Annotationstypen (engl. annotation types)
sind gemäß [JLS3] § 9.6 spezielle
Interfaces. Die Member von
Annotationstypen sind methodenartig
definiert und werden meist einfach als
„Elemente“ (engl. annotation type
elements) bezeichnet. (Der entsprechende
TGraphen-Typ wurde
AnnotationField genannt. Dies
entspricht allerdings nicht der
Terminologie der JLS.) Als Member gilt
für die Elemente von Annotationstypen
die bereits oben festgestellte Eigenschaft
Es gilt die Bereits für den public-Modifier von
Interface-Membern festgestellte Relevanz: Die Vergabe
anderer Sichtbarkeiten als public ist syntaktisch
verboten.
Relevante Constraint-Regeln: Acc-1, Sub-1, Dyn-1.
55
Spracheigenschaft
Relevanz für Refaktorisierungen
([JLS3] § 9.1.5), dass alle Member eines
Interfaces implizit public sind.
Tabelle 10: Impliziter Modifier für Annotationstyp-Elemente
4.3.7
Abgrenzung
Im Rahmen der Zusammenstellung der impliziten Quelltext-Elemente, die für die Erzeugung von
Sichtbarkeitsconstraints relevant sind, wurden weitere Kandidaten untersucht, für die jedoch
festgestellt wurde, dass eine Repräsentation im TGraphen nicht erforderlich ist. Diese werden im
Folgenden dokumentiert.
4.3.7.1
Elemente ohne implizite Modifier
Die folgenden Quelltext-Elemente können keine impliziten Modifier aufweisen:
•
•
•
Konstruktoren: Gemäß [JLS3] § 8.8.3 kann im Rahmen einer Konstruktor-Definition
ausschließlich ein Access-Modifier angegeben werden. Fehlt dieser, so ist damit DefaultSichtbarkeit deklariert.
Formale Methoden-Parameter: Der gemäß [JLS3] § 8.4.1 einzig erlaubte Modifier für die
formalen Parameter einer Methode ist final. Fehlt der final-Modifier, so wird er nicht
implizit angenommen.
Typ-Parameter (von Klassen, Interfaces und Methoden): Gemäß [JLS3] § 18.1 sind keine
Modifier möglich.
4.3.7.2
Implizite Typkonvertierungen
[JLS3] § 5 beschreibt ausführlich unterschiedliche gewissermaßen implizite TypkonvertierungsMechanismen. Dabei wird der Typ S eines Ausdrucks abhängig vom Kontext, in dem er verwendet
wird, in einen anderen Typ T konvertiert, der es dem Kontext erlaubt, den Ausdruck wie einen
Ausdruck des Typs T zu behandeln. Die Konvertierung erfolgt dabei bereits durch den Compiler
und kann ggf. eine zusätzliche Laufzeitprüfung erfordern.
Grundsätzlich werden im genannten Kapitel elf Konvertierungstypen (siehe [JLS3] § 5.1) und fünf
relevante Kontextarten beschrieben. In allen Fällen einer impliziten Typkonvertierung wird die
Konvertierung durch den Kontext ausgelöst. Dabei entspricht der Zieltyp der Konvertierung in vier
der fünf Kontextarten jeweils einem bereits im Kontext sichtbaren Typ:
•
•
•
•
Bei der Assignment Conversion (siehe [JLS3] § 5.2) ist dies der deklarierte Typ der
Variablen, an die zugewiesen wird.
Bei der Method Invocation Conversion (siehe [JLS3] § 5.3) ist es der deklarierte Typ der
Methoden-Parameter.
Bei der String Conversion (siehe [JLS3] § 5.4) ist der Zieltyp immer String.
Bei der Casting Conversion (siehe [JLS3] § 5.5) ist der Zieltyp immer im Cast-Operator
deklariert (siehe [JLS3] § 15.16).
56
Die fünfte Kontextart (Numeric Promotion, siehe [JLS3] § 5.6) sind arithmetische Operatoren und
hat ausschließlich Einfluss auf die Konvertierung numerischer Datentypen, die alle immer sichtbar
sind.
Aus dem Gesagten ergibt sich, dass es in keinem Fall zu einer Verletzung von
Sichtbarkeitsconstraints kommen kann, da diese ansonsten auch ohne die Typkonvertierung durch
den jeweiligen Kontext verletzt gewesen sein müssten.
4.3.7.3
Implizites this-Schlüsselwort
Hat ein Methoden-Aufruf (einer nicht-statischen Methode) die Form name() oder ein Feld-Zugriff
(eines nicht-statischen Feldes) die Form name = <Wert>, so ist das Empfänger-Objekt dieser
Referenzen implizit this, so als ob der Zugriff über this.name() oder this.name = Wert
erfolgt wäre. Dies bedeutet allerdings nicht, dass analog zu anderen impliziten Java-Elementen von
einem impliziten this-Schlüsselwort auszugehen wäre. In der [JLS3] wird kein solches implizites
this beschrieben, sondern es heißt lediglich im Kapitel über statische Methoden ([JLS3] §
8.4.3.2): „An instance method is always invoked with respect to an object, which becomes the
current object to which the keywords this and super refer during the execution of the method body.“
this kann demgemäß zwar als Empfänger-Objekt angenommen werden, ist aber nicht implizit im
Quelltext vorhanden.
Unabhängig davon, ob ein implizites this-Schlüsselwort bei einem Methoden-Aufruf oder FeldZugriff in der beschriebenen Form angenommen werden kann (wofür es keine Hinweise gibt), ist
mit Blick auf den Empfänger-Typ der jeweiligen Referenz auch keine Repräsentation erforderlich.
Denn die Referenz erfolgt vom Methoden-Aufruf (Knoten-Typ MethodInvocation) zum Typ des
Empfänger-Objekts, und dies ist in diesem Fall genau der Typ, der die Referenz enthält. Analoges
gilt für den Feld-Zugriff. Die benötigte Information kann also auch ohne die Annahme eines
impliziten this-Schlüsselworts ermittelt und repräsentiert werden.
4.4
Repräsentation der Override-Beziehung
Im Abschnitt 4.3.3. Implizite Erweiterung von Object wurde bereits erwähnt, dass die Sichtbarkeit
einer überschreibenden Methode nicht geringer sein darf als die der überschriebenen (ConstraintRegel Sub-1). Daraus ergibt sich unmittelbar die Relevanz der Override-Beziehung für die
Generierung der Sichtbarkeitsconstraints. Weitere relevante Constraint-Regeln sind Sub-2, Dyn1und Dyn-2.
Die Ermittlung der Override-Beziehungen im angepassten TGraphen hat auf Basis der Spezifikation
der Override-Beziehung zu erfolgen, die in [JLS3] § 8.4.8.1 gegeben wird.
4.5
Repräsentation statischer Bindungen
Statische Bindungen stellen die Beziehung von Referenzen auf Deklarationselemente dar und sind
daher für mehrere Constraint-Regeln relevant: Acc-1, Acc-2, Inh-1 und Inh-2. Im Folgenden
wird die Repräsentation statischer Bindungen im GraBaJa-TGraphen separat für Methoden-Aufrufe
und Feld-Zugriffe dargestellt.
57
4.5.1
Methoden- und Konstruktoren-Aufrufe
Wie bereits im Abschnitt 3.7.2.4. Subtyp-Beziehungen und Methoden-Aufrufe gezeigt wurde,
existiert im GraBaJa-TGraphen der Kantentyp IsDeclarationOfInvokedMethod, der als
Kandidat zur Repräsentation statischer Methoden-Bindungen in Betracht kommt. Da in
[BALD2008] keine weitere Dokumentation zum Gehalt dieser Kante entnehmbar ist und auch sonst
keine Dokumentation zu dieser Fragestellung gefunden werden konnte, war für die Zwecke der
vorliegenden Arbeit zu untersuchen, ob durch diesen Kantentyp tatsächlich statische Bindung im
Sinne der Spezifikation von [JLS3] § 15.12 repräsentiert wird.
Auf Anfrage bei den Autoren von [BALD2008] ergab sich, dass [JLS3] § 15.12 nicht als Vorlage
für die Implementierung des Extraktions-Algorithmus verwendet wurde. Das informell formulierte
Ziel der Implementierung war vielmehr, die „speziellst mögliche“ Methoden-Deklaration zu finden
und den Methoden-Aufruf mit dieser zu verknüpfen. Dies entspricht allerdings bereits weitgehend
den Vorgaben der [JLS3], die in § 15.12.1 feststellt: „There may be more than one such method, in
which case the most specific one is chosen.“ Die Ausführungen der [JLS3] zu diesem Thema
umfassen mehr als 40 Seiten und behandeln zahlreiche sehr spezielle Konstellationen. Eine
Überprüfung all dieser Konstellationen hätte den Rahmen dieser Arbeit überschritten. Für die
Zwecke dieser Arbeit wurde daher eine Reihe von Konstellationen ausgewählt, die den Großteil der
in der Praxis vorkommenden statischen Methoden-Bindungen abdecken. Für jede dieser
Konstellationen wurde ein Quelltext-Beispiel erstellt und der generierte TGraph überprüft. Im
Folgenden werden die überprüften Formen für Methoden-Aufrufe wiedergegeben.
Beispiel
Beschreibung
mymethod();
Einfacher Methoden-Aufruf einer innerhalb der
Klasse deklarierten Methode.
this.mymethod();
Methoden-Aufruf auf this.
super.superklassenmethode();
Methoden-Aufruf auf super.
feld.ueberladeneMethode("");
Aufruf einer überladenen Methode.
feld.interfaceMethode();
Aufruf einer Methode, wobei feld mit einem
Interface-Typ deklariert ist. Liegt keine
Implementierung des Interfaces vor, so wird an
die Interface-Deklaration gebunden, andernfalls
an die spezifischste Implementierung.
Klasse.statischeMethode();
Aufruf einer statischen Methode.
ueberschriebeneMethode();
Aufruf einer überschriebenen Methode.
variableStelligkeit("", "", "");
Aufruf einer Methode mit variabler Stelligkeit.
Klasse.<String>generischeMethode();
Aufruf einer statischen generischen Methode.
58
Beispiel
Beschreibung
methodeMitTypParametern(mylist);
Aufruf einer Methode mit generischem Typ als
Parametertyp. Dies funktioniert auch bei Type
Erasure. Im Beispiel wurde mylist mit List
deklariert, während die Methoden-Deklaration
List<String> als Parameter-Typ deklariert.
Tabelle 11: Überprüfte Formen statischer Methoden-Bindung
In all diesen Fällen repräsentierte die IsDeclarationOfInvokedMethod-Kante die statische
Methoden-Bindung gemäß [JLS3] § 15.12 korrekt. Konstruiert man allerdings ein komplexeres
Beispiel, so wird eine falsche Bindung ermittelt. Gegeben seien die beiden folgenden
Klassendefinitionen innerhalb der Datei AuessereKlasse.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AeussereKlasse {
class Testklasse {
public String toString(){return "AuessereKlasse.Testklasse";}
}
public static void main(String[] args) {
new AeussereKlasse().test1method();
}
void method() {
System.out.println(new Testklasse().toString());
class Testklasse{
public String toString(){return "AuessereKlasse.method().Testklasse";}
}
}
}
class Testklasse{
public String toString(){return "Testklasse";}
}
Führt man dieses Programm aus, so lautet die Ausgabe:
AuessereKlasse.Testklasse
Diese entspricht dem korrekten voll qualifizierten Pfad der Klasse, an die der Compiler bindet.
Untersucht man die IsDeclarationOfInvokedMethod-Kante für den Aufruf der toString()Methode in Zeile 9, so wird dieser jedoch nicht an die Klassen-Definition aus Zeile 2 (voll
qualifizierter Pfad AuessereKlasse.Testklasse), sondern an die Klassen-Definition aus Zeile
15 (voll qualifizierter Pfad Testklasse) gebunden. Dies ist im Sinne der statischen Bindung die
falsche Klasse.
Für die Zwecke dieser Arbeit ist damit zunächst gezeigt, dass der Bindealgorithmus des GraBaJaFaktenextraktors für Methoden-Aufrufe nicht in allen Fällen die statischen Bindungen repräsentiert.
Dies ist nicht notwendigerweise als Fehler zu betrachten, da es – wie oben erwähnt – nicht das
Entwicklungsziel war, die statischen Bindungen im Sinne von [JLS3] § 15.12 in den TGraphen
aufzunehmen.
59
Andererseits hat sich gezeigt, dass der Algorithmus für eine große Zahl von in der Praxis
vorkommenden Fällen tatsächlich die statische Bindung repräsentiert. Es wäre sicherlich möglich,
den Algorithmus des Faktenextraktors auch für Sonderfälle wie den oben konstruierten so
anzupassen, dass die Bindung der Spezifikation aus [JLS3] § 15.12 entspricht. Für die Zwecke
dieser Arbeit wurde entschieden, eine solche Korrektur des Faktenextraktors nicht vorzunehmen.
Hierfür sind zwei Gründe zu nennen:
•
Im Abschnitt 7.2.2. Wartbarkeit des Faktenextraktors wird näher erläutert, warum
Anpassungen des Faktenextraktors grundsätzlich problematisch sind und vermieden werden
sollten.
•
Eine Anpassung des Faktenextraktors löst nicht das Problem, dass Bindungen auch für
nachträgliche Anreicherungen des TGraphen aufgelöst werden können müssen. Dies muss
jedoch ohnehin außerhalb des Faktenextraktors erfolgen, so dass eine NeuImplementierung in jedem Fall notwendig wäre. Näheres hierzu wird in den Abschnitten
4.5.3. Ermittlung statischer Bindungen nach Faktenextraktion und 8.1.1. NeuImplementierung der statischen Bindungen ausgeführt.
Zugleich wurde jedoch entschieden, die IsDeclarationOfInvokedMethod-Kante vorläufig so
zu betrachten „als ob“ sie die statischen Bindungen korrekt repräsentierte. Andernfalls wäre
beispielsweise eine Implementierung der Referenz von Methoden-Aufrufen auf Empfänger-Typen
(siehe Kapitel 4.6. Referenzierung von Empfänger-Typen) nur schwer möglich gewesen. Um
dennoch eine Implementierung vornehmen zu können, wurde hier die
IsDeclarationOfInvokedMethod-Kante im Sinne der statischen Bindung eingesetzt. Dies
erscheint zumindest vor dem Hintergrund, dass diese Interpretation in der überwiegenden Mehrzahl
der überprüften Fälle zutrifft, für die Zwecke dieser Arbeit gerechtfertigt.
4.5.2
Feld-Zugriffe
Analog zu Methoden-Aufrufen existiert im GraBaJa-TGraphen für Feld-Zugriffe ein Kantentyp
IsDeclarationOfAccessedField, welche einen FieldAccess mit einer
VariableDeclaration verknüpft. Gemäß [JLS3] § 15.11 kann ein Feld-Zugriff grundsätzlich
entweder über einen sogenannten Primary Expression ([JLS3] § 15.8) oder über super erfolgen.
Die folgende Tabelle gibt eine Übersicht der wichtigsten Formen von Feld-Zugriffen.
60
Beispiel
Beschreibung
x = feld;
Zugriff auf ein innerhalb der Klasse deklariertes
Feld und Zugriff auf Methoden-Argumente.
x = objekt.feld;
Zugriff auf das öffentliche Feld eines anderen
Objekts. objekt kann dabei auch der Name
eines Methoden-Arguments sein.
x = this.feld;
Zugriff über this.
x = super.feld;
Zugriff über super.
x = Klasse.statischesfeld;
Zugriff auf ein statisches Feld.
int i = new String[10].length;
Zugriff über Array Creation Expression gemäß
[JLS3] § 15.10.
x = new Klasse().feld;
Zugriff über Class Instance Creation Expression
gemäß [JLS3] § 15.9.
x = array[0].feld;
Zugriff über ArrayAccess Expression gemäß
[JLS3] § 15.13.
Tabelle 12: Überprüfte Formen statischer Feld-Bindung
Alle bis auf eine dieser Zugriffsformen wird korrekt im TGraphen repräsentiert: Der Zugriff über
einen Array Creation Expression wird nicht repräsentiert. Dies wurde als Fehler an die Betreuer des
IST gemeldet und soll in einer künftigen GraBaJa-Version behoben werden. Aus der Perspektive der
Sichtbarkeitsconstraints ist dies jedoch nicht relevant, da das length-Feld im Array-Typ nur
implizit deklariert ist und seine Sichtbarkeit auch nicht modifiziert werden kann. Für die Zwecke
dieser Arbeit kann daher der Kantentyp IsDeclarationOfAccessedField als Repräsentation
der statischen Bindung von Feld-Zugriffen interpretiert werden.
Folgende Besonderheiten sind festzustellen:
•
•
4.5.3
Auch der Zugriff auf die linke Seite einer Zuweisung wird als FieldAccess repräsentiert
und mittels der IsDeclarationOfAccessedField-Kante an die Deklaration der
Variablen gebunden.
Auch der Zugriff auf super und this wird als FieldAccess repräsentiert. Die
IsDeclarationOfAccessedField-Kante wird dann direkt an die ClassDefinition
gebunden, auf die sich super oder this bezieht.
Ermittlung statischer Bindungen nach Faktenextraktion
Da der GraBaJa-Faktenextraktor die Bindungen, die durch die Kantentypen
IsDeclarationOfInvokedMethod und IsDeclarationOfAccessedField repräsentiert
werden, bereits beim Einlesen des Quelltexts auflöst, werden die statischen Bindungen für
nachträglich in den TGraphen aufgenommene Deklarationen oder Referenzen nicht im TGraphen
repräsentiert. Der Algorithmus des Faktenextraktors ist ohne Anpassungen für diesen Zweck nicht
verwendbar, da er auf Ebene des vom ANTLR-generierten Parser erzeugten AST und den
zugehörigen Symboltabellen arbeitet. Hiervon betroffen sind die folgenden Anreicherungen:
61
•
•
•
Ergänzte implizite super()-Aufrufe müssten an die Deklaration des aufgerufenen
Konstruktors gebunden werden.
An hinzugefügte implizite Konstruktoren müssten die Aufrufe dieser Konstruktoren
gebunden werden.
Nach der Aufnahme der impliziten Erweiterung von Object müssten Aufrufe der ObjectMethoden an die Deklarationen dieser Methoden in Object gebunden werden.
Referenzen des eigenen Quelltexts auf Deklarationen, die in externen Bibliotheken
vorkommen, müssten nach der Faktenextraktion externer Bibliotheken (siehe Kapitel
4.8. Repräsentation von Java-Elementen externer Bibliotheken) an diese Deklarationen
gebunden werden.
Um für diese und eventuelle künftige nachträgliche Anreicherungen des TGraphen die statischen
Bindungen ermitteln zu können, müssen die Bindealgorithmen außerhalb des GraBaJaFaktenextraktors neu implementiert werden. Dies wird näher im Abschnitt 8.1.1. NeuImplementierung der statischen Bindungen ausgeführt.
•
4.6
Referenzierung von Empfänger-Typen
Für zahlreiche Constraint-Regeln ist der Empfänger-Typ eines Methoden- oder Konstruktor-Aufrufs
sowie eines Feld-Zugriffs relevant: Acc-2, Inh-1, Inh-2 sowie Ovr. Die Ermittlung der
Empfänger-Typen ist wie folgt vorzunehmen:
•
Für Feld-Zugriffe entspricht der Empfänger-Typ dem Typ, an den statisch gebunden wird.
•
Bei statischen Methoden ist der Empfänger-Typ gleich dem Typ, in dem die Methode
deklariert ist.
•
Bei einem Aufruf nichtstatischer Methoden in der Form <Methodenname>() ist das
Empfänger-Objekt implizit this und somit der Empfänger-Typ gleich der Klasse, in welcher
der Methoden-Aufruf enthalten ist.
•
Bei einem Aufruf nichtstatischer Methoden in der Form
<Ausdruck>.<Methodenname>(...) ergibt sich der Empfänger-Typ aus dem statischen
Typ des Ausdrucks, der dem Methodennamen vorangestellt ist. Wie dieser ermittelt wird, ist
für jede mögliche Ausdrucksform [JLS3] § 15 zu entnehmen.
4.7
Fehlerhafte Lokationsinformation für Klassen
Im Rahmen der Untersuchung von TGraphen ist aufgefallen, dass im Falle methodenlokal
definierter Klassen der voll qualifizierte Klassenname, der im GraBaJa-TGraphen mittels des
Attributs fullyQualifiedName für die ClassDefinition erzeugt wird, ggf. uneindeutig sein
kann. Das folgende Beispiel veranschaulicht das Problem:
62
package mypackage;
public class AeussereKlasse {
class InnereKlasse {
public String toString(){
return "AuessereKlasse.InnereKlasse";
}
}
void method() {
class InnereKlasse {
public String toString(){
return "AuessereKlasse.method().InnereKlasse";
}
}
}
}
Generiert man für dieses Beispiel den TGraphen, so werden zwei ClassDefinition-Instanzen
erzeugt, deren fullyQualifiedName-Attribut den identischen Wert
mypackage.AeussereKlasse.InnereKlasse hat. Dies würde es bei der weiteren Analyse des
Quellcodes auf Basis der TGraphen-Repräsentation erschweren, sich jeweils auf die korrekte
InnereKlasse zu beziehen.
4.7.1
Abgrenzung
Das Problem ist nur für Klassen-Definitionen relevant. Aus [JLS3] § 14.3 bzw. aus dem Fehlen
entsprechender Beschreibungen für Interfaces oder Enums geht implizit hervor, dass ausschließlich
Klassen-Definitionen und nicht Interface- oder Enum-Definitionen methodenlokal erfolgen können.
Der Eclipse-Java-Compiler bestätigt dies durch die Meldung „The member interface ... can only be
defined inside a top-level class or interface“ (und analog für Enum-Definitionen). Demgemäß ist
das fullyQualifiedName-Attribut ausschließlich für Klassen-Definitionen anzupassen.
4.8
Repräsentation von Java-Elementen externer Bibliotheken
Die Möglichkeit zur Ausführung einer bestimmten Refaktorisierung kann von Eigenschaften
externer Bibliotheken abhängen, da die Deklarationen (und ggf. auch Referenzen) in diesen
Bibliotheken in vielfältiger Weise mit dem eigenen Quelltext in Verbindung stehen können.
Da jedoch für externe Bibliotheken der Quellcode nicht vorliegt und der Faktenextraktor des
GraBaJa-Projekts nicht in der Lage ist, Fakten aus dem Java-Bytecode (im Folgenden jeweils kurz
als „Bytecode“ bezeichnet) zu generieren, wären derartige Eigenschaften des betrachteten SoftwareSystems unzugänglich. Es ist daher ein Faktenextraktor für Java-Bytecode erforderlich, welcher
mindestens diejenigen Programmelemente in den TGraphen überführt, welche von außen potentiell
sichtbar sind oder welche selbst Teile des eigenen Quelltexts referenzieren.
63
4.9
Markierung der Veränderlichkeit von Quelltext-Elementen
Wie in [STEI2010] beschrieben, erfolgt die Lösung von Refaktorisierungs-Problemen bei einem
constraintbasierten Ansatz über Standardalgorithmen, welche die Constraint-Variablen und deren
Relationen als Eingabe erhalten. Eine Constraint-Variable steht für ein potentiell veränderliches
Quelltext-Element. Beim Generieren der Constraint-Variablen ist es somit notwendig zu wissen, ob
das betreffende Quelltext-Element tatsächlich veränderlich oder – wie z. B. bei Elementen der JavaKlassenbibliothek oder aus externen Bibliotheken – einer Änderung durch eine Refaktorisierung gar
nicht zugänglich ist. Im TGraphen sind daher die veränderlichen Elemente als solche zu markieren.
4.10
Rückreferenzierung des Quelltexts und Kommentare
Die Rückreferenzierung auf den ursprünglichen Quelltext ist gegeben und wurde im Rahmen der
Beschreibung des Metamodells im Abschnitt 3.7.2.5. Rückreferenzierung des Quelltexts dargestellt.
Ebenso werden Kommentare und ihre Position im Quelltext mittels der Knotentypen
SingleLineComment und MultiLineComment und des Kantentypen IsCommentIn im
TGraphen repräsentiert.
64
5
Dokumentation der vorgenommenen Anpassungen
5.1
Beispiel für eine Anreicherung
In diesem Abschnitt soll ein durchgehendes Beispiel für eine der durchgeführten Anpassungen
gegeben werden, die sowohl eine Erweiterung des Metamodells als auch eine Anreicherung des
generierten TGraphen erforderte. Gefordert wird gemäß Abschnitt 4.3.2. Implizite KonstruktorDefinitionen die Aufnahme impliziter Konstruktor-Definitionen in den TGraphen und ihre
Markierung als implizit, d. h. im expliziten Quelltext nicht vorhanden.
Zu diesem Zweck ist es zunächst notwendig, ein neues Attribut implicit für
ConstructorDefinition-Instanzen einzuführen. In der Schemadatei wird hierfür der
Spezifikation von ConstructorDefinition dieses Attribut hinzugefügt:
VertexClass ConstructorDefinition : Member { implicit : Boolean };
Nach einem GraBaJa-Build steht dieses Attribut zur Verfügung.
Um alle impliziten Konstruktor-Definitionen in den TGraphen aufzunehmen, müssen nun die
ClassDefinition-Instanzen durchlaufen und einzeln überprüft werden. Das Finden aller
ClassDefinition-Instanzen des TGraphen ist mittels einer GReQL-Abfrage möglich:
GreqlEvaluator eval = new GreqlEvaluator(
"from x:V{ClassDefinition}" +
" reportSet x" +
" end", tgraph, null);
eval.startEvaluation();
Set<ClassDefinition> cdset = new HashSet<ClassDefinition>();
for (JValue res : (JValueSet)eval.getEvaluationResult()) {
cdset.add((ClassDefinition)res.toObject());
}
Die Konvertierung von JValue-Objekten in ClassDefinition-Objekte ist dabei notwendig, da
der GReQL-Auswerter ein JValueSet generiert. Die ClassDefinition-Objekte können nun
einzeln daraufhin überprüft werden, ob eine implizite Konstruktor-Definition in den TGraphen
aufzunehmen ist. Dies ist genau dann der Fall, wenn noch keine explizite Konstruktor-Definition
vorliegt. Der Pfad von einer ClassDefinition zu einer ConstructorDefinition ist eindeutig
definiert, so dass hier die reachableVertices()-Methode zum Einsatz kommen kann:
classdef.reachableVertices(ConstructorDefinition.class,
new PathElement(IsClassBlockOf.class, EdgeDirection.IN),
new PathElement(IsMemberOf.class, EdgeDirection.IN));
Ist das gelieferte Set leer, so muss eine implizite Konstruktor-Definition in den TGraphen integriert
werden. Zu diesem Zweck ist zunächst der Block der Klasse zu besorgen, in welchen die
Konstruktor-Definition aufzunehmen ist:
65
Set<Block> blockset = classdef.reachableVertices(Block.class,
new PathElement(IsClassBlockOf.class, EdgeDirection.IN));
Aus diesem Set kann das einzige Ergebnis-Element verwendet werden, da es zu jeder Klasse nur
einen Block geben kann. Nun werden die neuen Instanzen erzeugt und verknüpft:
ConstructorDefinition constrdef = tgraph.createConstructorDefinition();
Identifier identifier = tgraph.createIdentifier();
identifier.set_name(classdef.get_name());
Block body = tgraph.createBlock();
tgraph.createIsNameOfConstructor(identifier, constrdef);
tgraph.createIsBodyOfConstructor(body, constrdef);
constrdef.set_implicit(true);
Hierdurch werden zunächst Instanzen des Typs ConstructorDefinition und Identifier
erzeugt, die mittels einer IsNameOfConstructor-Kante verknüpft werden. Für den Konstruktor
wird ein leerer Block erzeugt, welcher mittels IsBodyOfConstructor mit diesem verknüpft wird.
Schließlich wird das neue implicit-Attribut gesetzt. Die ConstructorDefinition ist nun
noch dem oben gefundenen Block der gerade betrachteten ClassDefinition zuzuordnen, indem
eine IsMemberOf-Kante zwischen diesen erzeugt wird. Damit ist die Anreicherung abgeschlossen:
tgraph.createIsMemberOf(constrdef, block);
5.2
Vorbereitung des TGraphen
Für einige der vorgenommenen Anpassungen hat es sich der folgende Sachverhalt als problematisch
erwiesen:
•
Klassen-Definitionen und der durch die Klasse repräsentierte Typ werden im TGraphen
durch zwei unterschiedliche Knotentypen repräsentiert: Die Klassen-Definition selbst als
ClassDefinition und der durch sie repräsentierte Typ als QualifiedType.
•
Der GraBaJa-Faktenextraktor erzeugt jedoch nicht in jedem Fall beide Instanzen. Existiert
im Graphen keine Typ-Referenz, so wird nur der ClassDefinition-Knoten erzeugt.
Soll nun im Laufe des Anreicherungsvorgangs, etwa durch zusätzlich aufgenommene
Deklarationen, eine Typ-Referenz zu einer Klasse hergestellt werden, so wäre immer zunächst zu
prüfen, ob der QualifiedType-Knoten auch bereits im TGraphen verfügbar ist. Um für alle
Abfragen und Anreicherungen zuverlässig davon ausgehen zu können, dass zu jeder
ClassDefinition auch ein QualifiedType verfügbar ist, wurde der TGraph zu Beginn des
Anreicherungsvorgangs um diese Knoten ergänzt.
5.3
Anreicherung der impliziten Quelltext-Elemente
Alle im Kapitel 4.3. Repräsentation impliziter Quelltext-Elemente geforderten Quelltext-Elemente
wurden in den TGraphen aufgenommen. Um diese als implizit kennzeichnen zu können, wurde wie
bereits im Kapitel 5.1. Beispiel für eine Anreicherung gezeigt ein Attribut namens implicit für
die betroffenen Knoten-Typen in das Metamodell des TGraphen aufgenommen.
66
5.4
Umsetzung der Override-Beziehung
Um die Anforderungen aus Kapitel 4.4. Repräsentation der Override-Beziehung zu erfüllen, wurden
im TGraphen die Kantentypen IsOverrdingMethodOf und IsOverriddenMethodBy
eingeführt. Im Rahmen der Anreicherung werden alle Konstellationen zwischen Klassen und
Superklassen, Klassen und Interfaces sowie Interfaces und Superinterfaces wie folgt berücksichtigt:
• In Klassen definierte Methoden werden verknüpft mit
◦ evtl. überschriebenen Methoden in einer ihrer Superklassen (extends-Beziehung
zwischen Klassen)
◦ evtl. überschriebenen Methoden in einem der implementierten Interfaces (implementsBeziehung zwischen Klassen und Interfaces) oder deren Superinterfaces (extendsBeziehung zwischen Interfaces).
• In Interfaces definierte Methoden werden mit evtl. überschriebenen Methoden in einem ihrer
Superinterfaces (extends-Beziehung zwischen Interfaces) verknüpft.
Bei der Feststellung der Override-Beziehung durch Vergleich der Methoden-Signaturen wird
Type Erasure berücksichtigt.
Die Override-Beziehung ist damit vollständig gemäß [JLS3] § 8.4.8.1 umgesetzt.
•
5.5
Umsetzung der Referenzierung von Empfänger-Typen
Um die Beziehung zu Empfänger-Typen im TGraphen repräsentieren zu können, wurden die
folgenden Kantentypen in das Metamodell des TGraphen aufgenommen:
QualifiedType ← IsReceiverOfInvokedMethod ← MethodInvocation
TypeSpecification ← IsReceiverOfAccessedField ← FieldAccess
Für Methoden-Aufrufe kommt als Empfänger-Typ nur ein Referenz-Typ (QualifiedType) in
Betracht, während bei Feld-Zugriffen auch ein primitiver Datentyp (BuiltInType) möglich ist.
Daher wird für Feldzugriffe der gemeinsame Supertyp von QualifiedType und BuiltInType
referenziert. Dieser Supertyp ist TypeSpecification.
Für die beiden neuen Kantentypen wurde der gemeinsame abstrakte Supertyp IsReceiverOf
deklariert. Dies ermöglicht es, unabhängig vom Referenztyp (Methoden-Aufruf oder Feld-Zugriff)
nach Empfänger-Typen zu suchen.
5.5.1
Einschränkungen der umgesetzten Anreicherung
Der <Ausdruck> eines Methoden-Aufrufs der Form <Ausdruck>.<Methodenname>(...)
kann zahlreiche unterschiedliche Formen annehmen. Die Regeln zur Ermittlung des Typs dieses
Ausdrucks nehmen weite Teile des umfangreichen Kapitels [JLS3] § 15 in Anspruch, so dass die
Ermittlung des Empfänger-Typs für alle potentiellen Ausdrucks-Arten den Rahmen dieser Arbeit
überschritten hätte. Die Anreicherung der IsReceiverOf*-Kanten erfolgte daher mit den
folgenden Einschränkungen:
•
Für Feld-Zugriffe erfolgt die Anreicherung der IsReceiverOfAccessedField-Kante
ohne Ausnahme.
67
•
•
Für Konstruktor-Aufrufe erfolgt die Anreicherung der IsReceiverOfInvokedMethodKante ohne Ausnahme.
Für Methoden-Aufrufe wurde die folgende Auswahl getroffen:
m()
feldname.m()
this.m()
super.m()
m1().m2()
// Nur dann, wenn statische Bindung zu m1 repräsentiert ist.
Klassenname.m()
5.6
Anpassung der Lokationsinformation für Klassen
Das im Kapitel 4.7. Fehlerhafte Lokationsinformation für Klassen wurde durch eine Neuermittlung
der Pfadinformation für alle Klassen gelöst. Eine eindeutige Abbildung des voll qualifizierten
Pfades ist wie folgt möglich und wurde vollständig umgesetzt:
<package>.<AuessereKlasse>. \
<Methodenname>(<Parametertyp-Liste>).(...).<EigentlicheDeklaration>
Dieser voll qualifizierte Name bezieht eventuelle Methoden, in denen Klassen definiert sein
können, mit ein. Die Aufnahme der Parametertyp-Liste ermöglicht zudem die Differenzierung der
Pfade im Falle überladener Methoden. Jeder Parametertyp ist dabei seinerseits in der voll
qualifizierten Form anzugeben, was eine rekursive Anreicherung erfordert. Dabei kann es nicht zu
einer Endlosrekursion kommen, da eine methodenlokal definierte Klasse nur innerhalb des Blocks
sichtbar ist, in dem sie definiert ist (siehe [JLS3] § 14.3) und somit in der formalen Parameterliste
oder gar außerhalb der Methode nicht verwendet werden darf.
Es werden ausschließlich diejenigen Klassen-Definitionen modifiziert, die als mutable markiert
sind. Für Quelltext-Elemente, die nicht mutable sind, liegt nur eine unvollständige Repräsentation
der Information vor, die für die Ermittlung des voll qualifizierten Namens notwendig wäre.
5.7
Prototypische Umsetzung eines Bytecode-Faktenextraktors
Der im Kapitel 4.8. Repräsentation von Java-Elementen externer Bibliotheken geforderte
Faktenextraktor für Java-Bytecode wurde im Rahmen dieser Arbeit prototypisch umgesetzt. Die
gewählte Lösung und der Umfang der Implementierung wird im Folgenden beschrieben.
5.7.1
Nutzung von BCEL zum Einlesen des Bytecodes
Für das Einlesen von Bytecode und die Extraktion der darin enthaltenen Java-Elemente wurde die
Bibliothek BCEL („Byte Code Engineering Library“, siehe [URL:BCEL]) ausgewählt. Da sich
bereits nach kurzer Evaluierung herausstellte, dass die von BCEL angebotenen Möglichkeiten für
die Zwecke dieser Arbeit ausreichend sind, wurden keine weiteren Werkzeuge evaluiert und die
Entscheidung für die Nutzung von BCEL getroffen.
68
5.7.2
Repräsentierte Elemente und Beziehungen
Eine vollständige Repräsentation der in externen Bibliotheken enthaltenen Java-Elemente ist zum
einen zur Generierung von Sichtbarkeitsconstraints nicht notwendig, zum anderen hätte dies den
Rahmen dieser Arbeit überschritten. Das Erzeugen von Teilgraphen aus Bytecode stellt daher im
Rahmen dieser Arbeit primär einen proof of concept dar, der für eine produktive Nutzung noch im
Detail ausgearbeitet werden müsste.
5.7.2.1
Nur public und protected Elemente
Es wurde davon ausgegangen, dass eigen-implementierte Quelltexte nur public oder protected
deklarierte Elemente externer Bibliotheken nutzen. Der Ausnahmefall, dass ein eigenimplementierter Quelltext innerhalb des Packages einer externen Bibliothek definiert ist, wurde
daher ignoriert. Dies gilt sowohl für die repräsentierten Typen als auch für die darin deklarierten
Methoden und Variablen.
5.7.2.2
Implizitheit wird nicht berücksichtigt
Ob das jeweilige Element im ursprünglichen Quellcode nur implizit oder explizit enthalten war, ist
dem Bytecode nicht mehr zu entnehmen und hat für die Constraint-Generierung keine Bedeutung.
Die Information würde lediglich beim Zurückschreiben von Änderungen eine Rolle spielen. Im
Rahmen von Refaktorisierungen werden jedoch keine Änderungen an Elementen des Bytecodes
vorgenommen. Das implicit-Attribut (siehe Kapitel 5.3. Anreicherung der impliziten QuelltextElemente) erhält für alle erzeugten Elemente den Wert false.
5.7.2.3
Klassen und Interfaces
Es werden alle (public oder protected) Klassen und Interfaces aus dem Bytecode ausgelesen
und in den TGraphen überführt. Enum-Definitionen sowie die Definition von Annotationstypen
bleiben unberücksichtigt. Alle Modifier der Klassen und Interfaces werden repräsentiert.
5.7.2.4
Methoden- und Variablen-Deklarationen
Es werden alle (public oder protected) Methoden und Variablen-Deklarationen in den
TGraphen überführt. Methoden werden mit allen Modifiern, ihrem Rückgabetyp und der Liste der
formalen Parameter erzeugt. Die Repräsentation von Variablen erfolgt mit ihrem deklarierten
Typen. Für die im Rahmen von Methoden- oder Variablen-Deklarationen verwendeten Typen gelten
einige Sonderbehandlungen:
•
•
Diese werden auch dann im TGraphen repräsentiert, wenn sie innerhalb einer externen
Bibliothek nicht als public oder protected deklariert sind. Ansonsten wäre eine
sinnvolle Repräsentation von Methoden- oder Variablen-Deklarationen nicht möglich.
Die Typen werden ausschließlich über eine QualifiedType-Instanz repräsentiert, nicht
über eine zugehörige ClassDefinition oder InterfaceDefinition. Letztere werden
nur dann erzeugt, wenn die Klasse oder das Interface in der externen Bibliothek public
oder protected deklariert ist. Dies lässt sich wie folgt rechtfertigen: Die
QualifiedType-Instanz enthält zum einen den voll qualifizierten Namen des Typen und
69
reicht somit aus, um ihn eindeutig zu identifizieren. Zum anderen werden auch SupertypBeziehungen über Kanten zur QualifiedType-Instanz abgebildet, so dass die eigentlichen
Klassen- und Interface-Deklarationen auch zur Ermittlung dieser Beziehungen überflüssig
sind.
5.7.2.5
Supertyp-Beziehungen
Supertyp-Beziehungen zwischen Klassen und Interfaces werden rekursiv in den TGraphen
überführt. Sowohl alle Superklassen als auch alle implementierten Interfaces einer Klasse werden
erzeugt. Für Interfaces werden alle erweiterten Interfaces in den TGraphen aufgenommen. Es
werden zum einen alle Supertyp-Beziehungen (zu public oder protected deklarierten Typen)
innerhalb des Bytecodes in den TGraphen überführt. Zum anderen sorgt bereits der Faktenextraktor
dafür, dass explizite Supertyp-Beziehungen vom Quelltext in die externe Bibliothek im TGraphen
repräsentiert sind. Denn der Faktenextraktor erzeugt in solchen Fällen für die Supertypen Instanzen,
ohne ihren Block und eventuelle Modifier zu kennen. Existieren solche Instanzen bereits, so werden
sie verwendet und um die im Bytecode ausgelesenen Modifier angereichert.
5.8
mutable-Attribut zur Markierung der Veränderlichkeit
Um veränderliche Quelltext-Elemente wie im Kapitel 4.9. Markierung der Veränderlichkeit von
Quelltext-Elementen gefordert als solche markieren zu können, wurde ein neues Attribut namens
mutable für alle Knoten und Kanten eingeführt. Dabei wurde entschieden, dass nur solche Knoten
als mutable markiert werden können, die in einer der für das Einlesen des TGraphen angegebenen
Quelldateien enthalten sind. Für Kanten wurde nach folgender Regel verfahren: Ist einer der beiden
Knoten, welche die Kante verknüpft, mutable, so ist auch die Kante mutable. Diese Regel lässt
sich wie folgt motivieren:
•
•
•
5.9
Alle Beziehungen zwischen Knoten, die sämtlich nicht veränderlich sind (also aus externen
Bibliotheken oder der Java-Klassenbibliothek stammen), sind ebenfalls nicht veränderlich.
Alle Beziehungen zwischen Knoten, die sämtlich veränderlich sind, sind ebenfalls
veränderlich.
Existiert zwischen einem veränderlichen und einem nicht veränderlichen Knoten eine
Beziehung, so muss davon ausgegangen werden, dass bei Modifikation des veränderlichen
Knoten auch die Kante zu ändern ist. Zum Beispiel kann der veränderliche Knoten gelöscht
werden, dann wäre auch die Kante zu löschen. Wäre die Kante nicht veränderlich, so würde
dies die Modifikationsmöglichkeiten des veränderlichen Knotens unzulässig einschränken,
was nicht einzusehen ist.
Reihenfolge der Anreicherung
Die Reihenfolge der Anreicherung ist relevant, da zwischen den unterschiedlichen ergänzten JavaElementen Abhängigkeiten bestehen. Die gewählte Reihenfolge wird im Folgenden erläutert:
1. Markierung der Veränderlichkeit von Quelltext-Elementen: Diese sollte vor dem Einlesen des
Bytecodes erfolgen, da alle aus Bytecode erzeugten Elemente nicht veränderlich sind.
2. Bytecode einlesen: Das Einlesen sollte vor dem Erzeugen der impliziten Quelltext-Elemente
erfolgen, da beispielsweise die impliziten super()-Aufrufe an Konstruktoren binden können,
die im Bytecode deklariert sind.
3. Implizite Java-Elemente: Die impliziten Java-Elemente werden nach der Extraktion des
70
Bytecodes in dieser Reihenfolge erzeugt:
• Implizite Object-Erweiterung.
Implizite Konstruktoren: Diese müssen vor den impliziten super()-Aufrufen erzeugt
werden, da erst im Anschluss den impliziten Konstruktoren auch implizite super()Aufrufe hinzugefügt werden können.
• Implizite super()-Aufrufe, Modifier sowie Erweiterung von Object und Enum.
4. Override-Beziehung: Die Override-Beziehung kann erst nach der Ergänzung der impliziten
Modifier sowie der impliziten Erweiterung von Object und Enum korrekt für alle Elemente
ermittelt werden.
5. Referenzierung der Empfänger.
6. Anpassung des voll qualifizierten Pfades.
•
71
6
Exemplarische Constraint-Generierung mittels TGraph
In diesem Kapitel soll am Beispiel der Constraint-Regel Sub-1 gezeigt werden, wie mittels des
erzeugten TGraphen die notwendigen Fakten zur Constraint-Generierung erzeugt werden können.
Gegeben seien die folgenden Java-Dateien:
Superclass.java:
public class Superclass {
void m0(Object o) {}
private void m1(int i) {}
protected void m2() {}
public String m3(String s) {return null;}
}
Subclass.java:
public class Subclass extends Superclass {
@Override void m0(Object o) {}
private void m1(int i) {}
@Override public void m2() {}
@Override public String m3(String s) {return null;}
}
Die Constraint-Regel Sub-1 verlangt, dass der Access-Modifier einer überschreibenden Methode
mindestens so groß sein muss wie der Access-Modifier der überschriebenen Methode. Im Beispiel
kommen drei Override-Beziehungen vor. Durch die im Kapitel 5.4. Umsetzung der OverrideBeziehung beschriebenen Anpassungen wurde die Override-Beziehung in den TGraphen
aufgenommen und kann nun effizient ausgelesen werden. Das folgende Codefragment zeigt die
grundlegende Vorgehensweise:
public void generateSub1(Java5 tgraph) {
for (MethodDeclaration overriding : findAllMethodDeclarations(tgraph)) {
for (IsOverridingMethodOf iomo :
overriding.getIsOverridingMethodOfIncidences(EdgeDirection.OUT)) {
MethodDeclaration overridden = (MethodDeclaration)iomo.getThat();
// So saehe die Constraint-Erzeugung aus:
addSub1Constraint(overriding, overridden);
// Fuer unsere Zwecke soll eine Ausgabe der Access-Modifier ausreichen:
printResult(getAccessModifierOfMethod(overridden),
getAccessModifierOfMethod(overriding));
}
}
}
72
addSub1Constraint(...) entspricht dabei einer gleichnamigen Methode der Klasse
Sub1Visitor aus der Implementierung der Access Modifier Modifier-Refaktorisierung, wurde
aber im Rahmen dieser Arbeit nicht ausimplementiert.
Die Schnittstellen-Methode getIsOverridingMethodOfIncidences(...) ermöglicht das
einfache Auffinden aller Override-Beziehungen für jede betrachtete Methoden-Deklaration. Die
Verfügbarkeit dieser Methode wurde durch die Aufnahme des Kantentyps
IsOverridingMethodOf im Metamodell des TGraphen erreicht.
Die Methode findAllMethodDeclarations(...) wird mittels einer GReQL-Abfage wie folgt
implementiert:
private Set<MethodDeclaration> findAllMethodDeclarations(Java5 tgraph) {
GreqlEvaluator eval = new GreqlEvaluator(
"from x:V{MethodDeclaration}" +
" reportSet x" +
" end", tgraph, null);
eval.startEvaluation();
Set<MethodDeclaration> methoddeclarations =
new HashSet<MethodDeclaration>();
for (JValue res : (JValueSet)eval.getEvaluationResult()) {
methoddeclarations.add((MethodDeclaration)res.toObject());
}
return methoddeclarations;
}
Um das gefundene Ergebnis für das Quelltext-Beispiel anschaulich zu machen, wurde eine kurze
Textausgabe der Access-Modifier für die Methoden-Paare mit Override-Beziehung implementiert.
Die Ermittlung der Access-Modifier kann dabei über eine Methode wie die folgende umgesetzt
werden:
private String getAccessModifierOfMethod(MethodDeclaration method) {
String modifier = "DEFAULT";
for (IsModifierOfMethod imom :
method.getIsModifierOfMethodIncidences(EdgeDirection.IN)) {
Modifier mod = (Modifier)imom.getThat();
Modifiers type = mod.get_type();
if (type.equals(Modifiers.PUBLIC) ||
type.equals(Modifiers.PROTECTED) ||
type.equals(Modifiers.PRIVATE)) {
modifier = type.toString();
break;
}
}
return modifier;
}
73
Eine Textausgabe könnte nun wie folgt aussehen:
Sub-1: Ueberschreibende Methode:
Ueberschriebene Methode :
Sub-1: Ueberschreibende Methode:
Ueberschriebene Methode :
Sub-1: Ueberschreibende Methode:
Ueberschriebene Methode :
6.1
PUBLIC
PUBLIC
PUBLIC
PROTECTED
DEFAULT
DEFAULT
Laufzeitverbesserte Variante
Der oben angegebene Beispiel-Quelltext iteriert mittels der Methode
findAllMethodDeclarations(...) über alle Methoden-Deklarationen des TGraphen. Dies ist
jedoch in der Regel für eine konkrete Refaktorisierung ein unnötig großer Aufwand, da von der
durchzuführenden Änderung meist nur eine kleine Auswahl von Methoden-Deklarationen betroffen
ist. In [STEI2011] wird daher vorgeschlagen, nur diejenigen Constraints zu generieren, die
tatsächlich für die Refaktorisierung notwendig sind. Wie dies auf der Basis von TGraphen aussehen
könnte, soll das folgende Beispiel zeigen.
Vorgegeben sei, dass durch die Refaktorisierung die Sichtbarkeit einer nichtstatischen MethodenDeklaration reduziert werden soll. Mit Blick auf die Sub-1-Constraint-Regel sind von dieser
Änderung alle Methoden betroffen, welche durch die betreffende Methode überschrieben werden.
Die Reduktion der Sichtbarkeit könnte dazu führen, dass nach der Änderung keine OverrideBeziehung mehr zwischen den Methoden besteht. Für diese spezielle Refaktorisierung könnte die
Ermittlung der betroffenen Methoden-Paare wie folgt aussehen:
public void generateSub1ReducedAccessibility(Java5 tgraph,
MethodDeclaration reduced) {
for (IsOverridingMethodOf iomo : reduced.getIsOverridingMethodOfIncidences(
EdgeDirection.OUT)) {
MethodDeclaration overridden = (MethodDeclaration)iomo.getThat();
addSub1Constraint(reduced, overridden);
}
}
Mittels der Schnittstellen-Methode getIsOverridingMethodOfIncidences(...) werden
ausschließlich die überschriebenen Methoden gefunden und durchlaufen, so dass keine
überflüssigen Constraints erzeugt werden.
74
7
Diskussion
Im Folgenden sollen wesentliche Erfahrungen, die im Rahmen der zuvor beschriebenen
Implementierungsarbeiten gemacht wurden, wiedergegeben und bewertet werden. Des Weiteren
werden durchgeführte Performance-Messungen für die Faktenextraktion dargestellt und
eingeschätzt.
7.1
Allgemeine Eignung für die Entwicklungsarbeit
In diesem Abschnitt werden einige Qualitäts-Kriterien beschrieben, die für Entwickler, die auf der
Basis des Graphenlabors (Refaktorisierungs-)Werkzeuge entwickeln möchten, besonders relevant
sind. Die hier gegebenen Einschätzungen erheben keinen Anspruch auf Vollständigkeit und sind
vielmehr im Sinne eines Erfahrungsberichts zu verstehen.
7.1.1
Verständlichkeit und Benutzbarkeit der Schnittstellen
Das Graphenlabor stellt dem Entwickler die Datenstruktur TGraph zur Verfügung und bietet für
dessen Handhabung umfangreiche Schnittstellen an. Diese Schnittstellen lassen sich wie folgt grob
unterteilen und werden in den folgenden Abschnitten separat diskutiert:
•
•
•
Methoden zum Navigieren im TGraphen
Die Nutzung von GReQL
Util-Methoden
7.1.1.1
Methoden zum Navigieren im TGraphen
Das Graphenlabor stellt eine ganze Reihe unterschiedlicher Methoden für das Navigieren im
TGraphen zur Verfügung, die dem Entwickler lediglich die Qual der Wahl lassen. Nach einiger
Übung erweist es sich als unproblematisch, Knoten oder Kanten im Graphen zu finden – sei es in
unmittelbarer Nachbarschaft oder über längere Pfade hinweg. Die Eigenschaften des TGraphen
bieten hierfür umfassende Möglichkeiten: Die Typisierung ermöglicht die Nutzung von Methoden
der Art get<Kantentyp>Incidences() von einem Knoten aus, die per Codegenerator aus dem
Metamodell miterzeugt werden. Ist eine derart spezielle Navigation einmal nicht gewünscht,
ermöglicht die Ordnung des TGraphen die Verwendung verschiedener getNext*()- oder
getFirst*()-Methoden, um Knoten oder Kanten effizient zu iterieren. Bei der Suche nach
speziellen Pfaden von einem Knoten zu einem anderen hat sich die reachableVertices()Methode als besonders hilfreich erwiesen. Sie erledigt über eine simple Syntax, wofür ansonsten ein
vergleichsweise aufwändiges Traversieren des Graphen nötig wäre. Und helfen all diese speziellen
Anwendungsfälle nicht weiter, so bieten GReQL-basierte Abfragen schließlich die maximale
Flexibilität, um beliebig komplexe Suchabfragen zu formulieren.
Es gibt allerdings auch noch Spielraum für Verbesserungen. So liefern etwa einige
Abfragemethoden immer eine typisierte Collection, obwohl sogar das Metamodell mehrere
Treffer für eine Abfrage verbietet. So kann z. B. zu einer ClassDefinition nur genau ein Block
existieren. Die folgende Abfrage
75
classdefinition.reachableVertices(Block.class,
new PathElement(IsClassBlockOf.class, EdgeDirection.IN));
liefert aber nichtsdestoweniger ein Set<Block>, aus welchem man sich anschließend das einzige
Element herausnehmen muss. So entsteht wiederholt Boilerplate Code, der kompakte Lösungen
komplexer aussehen lässt als nötig. Wünschenswert wäre generell, die jeweiligen Methoden in einer
Version zur Verfügung zu stellen, die genau ein Element des erfragten Typs liefert – auch in Fällen,
in denen zumindest syntaktisch mehrere Treffer möglich sind. Denn in der Praxis kommt es häufig
vor, dass auch in solchen Fällen nur genau ein Element gesucht wird, das der Entwickler auch
hinreichend genau spezifizieren kann, um sicher zu sein, dass es nur diesen einen Treffer gibt.
Solche Methoden könnten z. B. simple Wrapper um die bereits existierenden Methoden sein und für
den Fall, dass tatsächlich einmal mehr als ein Element gefunden wird, eine Exception werfen.
Die Ableitung des Metamodells aus der ANTLR-Javagrammatik und die Notwendigkeit zur
Repräsentation auch feingranularer Syntax-Elemente bringt es zudem mit sich, dass das Navigieren
im Graphen gelegentlich etwas komplex anmutet. Für analytische Zwecke wäre es wünschenswert,
Beziehungen in den TGraphen aufzunehmen oder Schnittstellen-Methoden anzubieten, die typische
analytische Fragestellungen widerspiegeln. Um beispielsweise alle Methoden einer Klasse zu
erhalten, muss man umständlich über eine IsClassBlockOf-Kante und eine Block-Instanz
navigieren, da die MethodDefinition der ClassDefinition nicht direkt zugeordnet ist. Im
Quellcode sieht eine solche Abfrage so aus:
classdefinition.reachableVertices(MethodDeclaration.class,
new PathElement(IsClassBlockOf.class, EdgeDirection.IN),
new PathElement(IsMemberOf.class, EdgeDirection.IN));
Dies ist eine vergleichsweise aufwändige Formulierung der sicher häufig gestellten Frage nach den
Methoden einer Klasse, die sich prinzipiell als simpler Methoden-Aufruf denken ließe:
// Diese Methode existiert aber nicht:
classdefinition.getMethodDeclarations();
// Die Methode koennte auch ueberladen sein, um ueber ganz bestimmte
// Methoden einer Klasse iterieren zu koennen, z. B.:
classdefinition.getMethodDeclarations(Modifiers.PUBLIC);
classdefinition.getMethodDeclarations(Modifiers.STATIC);
Für analytische Zwecke ist die Existenz bestimmter Elemente im Metamodell irrelevant. (Im obigen
Beispiel stört die zwischengeschaltete Block-Instanz, da jede Klassen-Definition über einen Block
verfügt.) Des Weiteren liegt der Fokus einer Abfrage oftmals auf bestimmten Knoten, die mit einem
gegebenen Knoten in Beziehung stehen, und nicht auf dem Pfad, der zu diesen Knoten führt.
Höherwertige Schnittstellen-Methoden würden das Navigieren im Javagraphen erleichtern und den
Quelltext lesbarer machen. Es folgen einige weitere Beispiele für sinnvolle SchnittstellenMethoden:
•
•
•
•
•
Finde alle Methoden/Felder/Konstruktoren einer Klasse.
Finde alle Interfaces, welche von der Klasse implementiert werden.
Finde alle Subtypen eines Typs.
Finde alle formalen Parameter einer Methode.
Finde zu einem gegebenen Element die Klasse, in welcher dieses Element implementiert ist.
76
•
•
Finde die Klassen- / Interface-Definition zu dem Typ, mit dem eine Variable deklariert ist /
welcher der Rückgabetyp einer Methode ist / welcher als Typ eines Methoden-Parameters
deklariert ist usw.
Finde alle Modifier eines Deklarationselements.
Finde den Access-Modifier eines Deklarationselements.
7.1.1.2
Die Nutzung von GReQL
Mit GReQL steht eine Abfragesprache zur Verfügung, welche die Mächtigkeit regulärer Ausdrücke
aufweist. Hinsichtlich der Leistungsfähigkeit der Sprache bleiben damit keine Wünsche offen. Die
Verfügbarkeit einer Abfragesprache legt die Separierung von Abfrage-Code und dem Rest der
Anwendung im Sinne eines Data Access Object (DAO)-Patterns nahe (siehe den J2EE-PatternKatalog in [ALUR2002]), wie man dies auch für SQL- oder Hibernate-Queries häufig sieht. Dabei
werden alle Queries in einer DAO-Klasse implementiert, in welcher somit die gesamte AbfrageLogik verwaltet werden kann. Dies fördert nicht nur die Wiederverwendung, sondern ermöglicht
auch z. B. im Falle späterer Metamodell-Änderungen, die eine Anpassung mehrerer Queries nach
sich ziehen, dass diese Anpassungen an genau einem Ort erfolgen können.
Die Sprachsyntax von GReQL ist gut verständlich und in vernünftiger Zeit erlernbar. Die
Anlehnung an die Syntax regulärer Ausdrücke für Zeichenketten, insbesondere die Operatoren *
und +, ermöglichen ein intuitives Verständnis der Pfadausdrücke. Andererseits bestehen
Pfadausdrücke auch zu großen Teilen aus Syntax-Elementen, die man sich erarbeiten muss und mit
denen man Erfahrungen zu sammeln hat, so dass eine gewisse steile Lernkurve in den ersten
Entwicklungstagen mit GReQL nicht vermieden werden kann. Hinzu kommt, dass eine effiziente
Formulierung von Abfragen auch eine detaillierte Kenntnis des Metamodells erfordert und auch
dessen Kennenlernen Zeit in Anspruch nimmt.
7.1.1.3
Util-Methoden
Das Graphenlabor bietet eine Reihe von Hilfswerkzeugen für den Umgang mit Graphen auf höherer
Ebene an. So existieren z. B. diverse Export-Formate, von denen sich insbesondere der Export in
das DOT-Format als nützlich erwiesen hat. Das DOT-Format lässt sich mittels frei verfügbarer
Werkzeuge wie Graphviz (siehe [URL:GRAPHVIZ]) in eine Abbildung des generierten TGraphen
konvertieren, die in einer beliebigen Grafik-Software betrachtet werden kann. Gerade für das
Verstehen einfacher Zusammenhänge hat sich diese Vorgehensweise als brauchbarer erwiesen als
der in das Graphenlabor integrierte TGraph-Browser. Dieser ermöglicht zwar eine interaktive
Ansicht des Graphen auch unter Nutzung von GReQL-Abfragen, doch ein schnelles Hin- und HerNavigieren im TGraphen mittels Mausklicks oder auch ein zügiges Aus- und Ein-Zoomen gelingen
in einer Grafik-Software besser.
7.1.2
Verwendbarkeit des Metamodells
Das GraBaJa-Metamodell hat sich als solider Ausgangspunkt erwiesen. Verbesserungspotential liegt
im Detail. Im Folgenden werden einige Ideen für mögliche Verbesserungen beschrieben, die sich im
Rahmen der Entwicklungsarbeit ergeben haben.
77
7.1.2.1
Wahl der Typ-Bezeichner
Die Terminologie wurde nicht in jeder Hinsicht kompatibel zur JLS gewählt, was gelegentlich das
Verständnis etwas erschwert. Beispielsweise sucht man den Begriff AnnotationField nicht nur
in der [JLS3] vergeblich – er ist vielmehr im gesamten englischen Sprachraum unüblich. Auch die
namentliche Unterscheidung zwischen Definitionen und Deklarationen (z. B. bei
MethodDeclaration und MethodDefinition) führt gelegentlich zu sprachlichen
Schwierigkeiten, wenn man sich auf die JLS beziehen will, in der diese Unterschiede nicht klar
spezifiziert sind und die oft auch dann von method declarations spricht, wenn das GraBaJaMetamodell eine MethodDefinition erzeugt. Etwas unpassend erscheint auch die Verwendung
des Typs IsSuperClassOfInterface für die extends-Beziehung zwischen Interfaces. Da die
Benennungen der Typen größtenteils aus der für ANTLR verwendeten Javagrammatik stammen
bzw. von dort übernommen wurden, ist die primäre Ursache für diese Benennungen dort zu suchen.
7.1.2.2
Kompositionen statt Aggregationen
Einige Aggregationen hätten als Komposition modelliert werden können. Bei einer Komposition
hängt die Lebensdauer eines Teilobjekts von der Lebensdauer des umfassenden Container-Objekts
ab. Dies ermöglicht die Nutzung entsprechender Operationen (wie z. B. dem Löschen des
Container-Objekts inklusive aller Teilobjekte durch einen einzigen Methoden-Aufruf) und hilft,
inkonsistente Graphen-Zustände zu vermeiden.
7.1.3
Reifegrad
Für die Entscheidung über die Verwendung einer Bibliothek ist es oftmals interessant, ihren
Reifegrad zu beurteilen: Weist die Bibliothek noch „Kinderkrankheiten“ auf, weil sie bisher kaum
verwendet wurde?
Bei der Beurteilung des Reifegrads muss zwischen dem Graphenlabor und GraBaJa unterschieden
werden. Während das Graphenlabor als ausgereift bezeichnet werden kann und im Rahmen der
Untersuchungen für diese Arbeit keinerlei Fehler festgestellt wurde, welcher der Funktionalität des
Graphenlabors zuzuordnen ist, sieht dies für GraBaJa anders aus:
•
•
•
•
•
•
Die Konstruktion voll qualifizierter Pfade war unvollständig, siehe Kapitel 4.7. Fehlerhafte
Lokationsinformation für Klassen.
Das Setzen der voll qualifizierten Pfade war fehlerhaft für Instanzen des Typs
QualifiedType. Während beispielsweise die zugehörige ClassDefinition einen
korrekten Pfad aufwies, wurde in QualifiedType lediglich der Typ-Name ohne Pfad
abgelegt.
Auf Verbesserungspotential des Metamodells wurde oben bereits hingewiesen.
Die Grammatik-Spezifikation für ANTLR weist aktuell knapp zwanzig unerledigte TodoVermerke und als „quick fix“ kommentierte Stellen auf.
Die Codierung des Faktenextraktors auf Basis von ANTLR gilt zudem im betreuenden
Lehrgebiet des IST der Universität Koblenz-Landau als schwer zu warten, siehe Abschnitt
7.2.2. Wartbarkeit des Faktenextraktors.
Zu GraBaJa existiert über die Studienarbeit [BALD2008] hinaus keinerlei Dokumentation.
Da seit dieser Studienarbeit jedoch Änderungen und Bugfixes erfolgt sind, kann der Stand
der Dokumentation nicht mehr als aktuell betrachtet werden.
78
•
GraBaJa wurde bislang noch in keinem größeren Referenzprojekt eingesetzt.
In der Zusammenschau dieser Sachverhalte muss davon ausgegangen werden, dass ein größeres
Entwicklungsprojekt auf der Basis von GraBaJa einen gewissen Zusatzaufwand für die Behandlung
von Problemen betreiben müsste, die bisher auf Grund mangelnder produktiver Nutzung noch
unbemerkt geblieben sind.
7.2
Erweiterbarkeit
Die Beurteilung der Erweiterbarkeit von TGraphen fällt geteilt aus. Während Erweiterungen des
Metamodells und nachträgliche Erweiterungen des vom Faktenextraktor erzeugten TGraphen
durchweg gut unterstützt werden, ist die Erweiterbarkeit des Faktenextraktors als problematisch
einzustufen.
7.2.1
Anreicherung des TGraphen
Metamodell-Anpassungen: Anpassungen des Metamodells sind nach kurzem Einlesen in die
Syntax der Schemadatei problemlos möglich. Die Ergänzung neuer Knoten- und Kantentypen
macht dabei nicht nur die neuen Typen für die Verwendung im TGraphen verfügbar, sondern auch
eine Reihe praktischer Schnittstellen-Methoden. Zu bedenken ist allerdings, dass MetamodellAnpassungen einen neuen Build des GraBaJa-Projekts erfordern.
Factory-Methoden: Für die Integration neuer Instanzen in den TGraphen hat sich die Nutzung von
Factory-Methoden der Art Java5.createClassDefinition() oder
Java5.createIsMemberOf(Member, Block) als sehr hilfreich erwiesen. Die Nutzung der
Factory-Methoden stellt sicher, dass der Graph geordnet bleibt – die Verwaltung der Knoten-,
Kanten- und Inzidenzreihenfolgen erfolgt für den Entwickler transparent.
Erzeugungsmethoden für mehrere zusammenhängende Elemente: In der Regel ist es wenig
sinnvoll, einzelne Instanzen isoliert im TGraphen zu erzeugen. So verfügt eine Klasse
(ClassDefinition) oder eine Methode (MethodDefinition) immer auch über einen Block
(Block). Member (wie z. B. VariableDeclaration) verfügen immer auch über einen Typ
(QualifiedType oder BuiltInType) usw. Für solche Konstellationen würde man sich
Erzeugungsmethoden wünschen, die syntaktisch eng gekoppelte Instanzen über einen einzigen
Aufruf erzeugen. Hierfür sollen einige Beispiele aufgeführt werden:
•
Die Erzeugung einer Klasse könnte über einen einzigen Aufruf der Art
Java5.createClassDefinitionWithBlock() eine ClassDefinition erzeugen, die
bereits über eine IsClassBlockOf-Kante mit einem Block verknüpft ist.
•
Bei der Verknüpfung von Typen stellt sich regelmäßig das Problem, dass man die
betreffende QualifiedType-Instanz zunächst im TGraphen finden muss, um sie dann z. B.
(bei Variablen-Deklarationen) mittels einer IsTypeOfVariable-Kante zu verknüpfen.
Derartige wiederkehrende Fleißarbeiten würde man gerne der Erzeugungsmethode
überlassen, indem man die Typen bei der Erzeugung einfach angibt:
Java5.createVariableDeclaration(<Deklarierende Klasse>,
<Deklarierter Typ>).
79
•
Will man einer Klasse ein Member hinzufügen, so muss man sich hierfür zunächst den
Block der Klasse besorgen. Dieser Zusatzschritt könnte durch eine Methode
Java5.addMemberMethod(<Deklarierende Klasse>, <MethodenDeklaration>) vermieden werden.
Zahlreiche weitere Beispiele ließen sich aufzählen. Bei umfangreichen Entwicklungsarbeiten würde
dies zum einen TGraphen vermeiden helfen, die syntaktisch inkorrekten Java-Programmen
entsprechen, zum anderen wäre der entstehende Quelltext kompakter und besser verständlich.
7.2.2
Wartbarkeit des Faktenextraktors
Die im Sinne der Wartbarkeit problematischste Komponente von GraBaJa ist der ANTLR-basierte
Faktenextraktor. Der größte Teil von dessen Anwendungslogik ist in der ANTLR-GrammatikSpezifikation java15.tree.g definiert: eine gut 2800 Zeilen lange Datei in spezieller Syntax,
deren Details in [URL:ANTLRGRAMMAR] eingesehen werden können. Die hier definierte
kontextfreie Grammatik ist durchsetzt mit sogenannten Actions, Java-Fragmenten in geschweiften
Klammern, welche u. a. den internen AST und eine Symboltabelle aufbauen, die den
Ausgangspunkt für die Erzeugung des TGraphen darstellen. Gespräche mit Mitarbeitern des IST
haben ergeben, dass es sich in der Vergangenheit als schwierig erwiesen hat, diese Datei punktuell
zu modifizieren. Da es seit dem Abschluss der Arbeit [BALD2008] keine Änderungen der JavaSyntax mehr gegeben hat, bestand hierzu auch keine Notwendigkeit mehr. Wenn jedoch mit der
nächsten Sprachversion Java 7 syntaktische Änderungen eintreten, so geht man beim IST derzeit
davon aus, dass diese nicht durch Anpassungen des ANTLR-Mechanismus umgesetzt werden,
sondern auf Basis eines neu zu entwickelnden Faktenextraktors.
7.3
7.3.1
Performance
Rahmenbedingungen
Die Performance-Messungen wurden unter folgenden Systemvoraussetzungen durchgeführt:
•
•
•
CPU: Intel(R) Core(TM) Duo P8400 2.26 GHZ
Arbeitsspeicher: 4 GB
Betriebssystem: Windows Vista (32 Bit), Service Pack 2
Als Referenz für die folgende ausführliche Beschreibung wurde der Quelltext des JUnit-Projekts in
Version 3.8.2 mittels des GraBaJa-Faktenextraktors eingelesen und der so erzeugte TGraph mit
allen implementierten Anreicherungen erweitert. Im Abschnitt 7.3.4. Vergleichsmessungen für
verschiedene Quelltexte werden Messungen für weitere Projekte einander gegenübergestellt.
Im Rahmen der ersten Performance-Messungen ergab sich, dass das Setzen der mutable-Attribute
einen überproportionalen Rechenaufwand verursachte. Da für dieses Setzen sämtliche Knoten und
Kanten des TGraphen durchlaufen werden und die Ermittlungslogik jeweils Abfragen an den
TGraphen erfordert, stieg die Laufzeit der Anreicherung des TGraphen um ein Vielfaches. Die
untenstehenden Laufzeit-Ergebnisse wurden daher ohne das Setzen der mutable-Attribute
ermittelt. Für die Problematik der Veränderlichkeit von Java-Elementen muss vor diesem
Hintergrund ggf. eine einfachere Lösung gefunden werden.
80
7.3.2
Laufzeit
Bereits der Faktenextraktor erzeugt einige Ausgaben zur Laufzeit:
processing 50 elements
time: 1.328s
1738 type specification(s) to resolve.
processing 1738 elements
time: 0.484s
615 field accesses to resolve.
processing 615 elements
time: 0.141s
1754 method invocations to resolve.
processing 1754 elements
time: 0.204s
Insgesamt benötigt die reine Faktenextraktion zwei Sekunden. Inklusive Anreicherung werden vier
Sekunden benötigt. Dabei werden 50 Java-Dateien eingelesen und ein TGraph mit rund 51000
Knoten und Kanten erzeugt.
7.3.3
Speicherverbrauch
Um einen Einblick in den Speicherverbrauch zu geben, wurde mittels des Monitoring-Werkzeugs
JConsole (siehe [URL:JCONSOLE]) der Heap-Verbrauch während der Faktenextraktion inklusive
Anreicherung beobachtet. Dies führte zu dem Ergebnis, das in der untenstehenden Abbildung
dokumentiert ist. Zu Beginn der Ausführung wurde manuell eine Garbage Collection ausgelöst
(Knick nach unten). Der Heap-Verbrauch sank dabei unter 1 MB. Während der Ausführung stieg
der Heap-Verbrauch bis knapp unter 10 MB an. Das anschließende Abfallen der Kurve entspricht
der Beendigung des Prozesses.
81
Abbildung 9: Heap-Monitoring während der Faktenextraktion des JUnit-Quelltexts
7.3.4
Vergleichsmessungen für verschiedene Quelltexte
Projekt
Anzahl Anzahl Knoten Laufzeit mit / ohne
Dateien und Kanten
Anreicherung
Maximaler HeapVerbrauch
Jester 1.37b
45
ca. 23.000
3s/2s
< 10 MB
JUnit 3.8.2
50
ca. 51.000
4s/2s
< 10 MB
HTMLparser 1.6
149
ca. 220.000
24 s / 12 s
< 40 MB
JHotDraw 6.01b
289
ca. 246.000
33 s / 13 s
< 50 MB
Eclipse Draw2D 3.4.2 245
ca. 262.000
56 s / 13 s
< 50 MB
Tabelle 13: Laufzeit- und Speichermessungen für diverse Quelltexte
82
7.3.5
Bewertung der Performance-Messungen
Wie sind diese Extraktionsdauern zu bewerten? Sicherlich wird man Wartezeiten von bis zu einer
Minute für größere Entwicklungsprojekte bei der Umsetzung interaktiver Werkzeuge vermeiden
wollen. Jedoch erscheinen diese Zeiten in Anbetracht von ca. 250.000 repräsentierten Knoten und
Kanten nicht unvernünftig hoch. Zudem wurde bereits im Abschnitt 2.3.2. Erweiterte
Faktenextraktion und Speicherung in Hashtabellen erwähnt, dass auch ein auf dem Eclipse-AST
basierender Ansatz mitunter Extraktionsdauern von mehreren Minuten erforderte, wenn diese
Zeiten auch mangels identischer Rahmenbedingungen nur eine vage Vergleichbarkeit ermöglichen.
Mehr als die Hälfte der Zeit wird vom Faktenextraktor aufgewandt: Ein Profiling mit dem
Werkzeug JProfiler (siehe [URL:JPROFILER]) ergab am Beispiel von JUnit, dass 55% der Zeit für
die Faktenextraktion (Parsen der Dateien, Mapping des AST auf den TGraphen, Auflösen der
statischen Bindungen) aufgewandt wird. Die nachfolgende Anreicherung erfordert für JUnit ca.
40% der Zeit. Da weder die reine Faktenextraktion noch die implementierten Anreicherungen auf
Laufzeit-Aspekte hin optimiert sind, ist durchaus von einigem Optimierungspotential hinsichtlich
der Laufzeit auszugehen.
Der Speicherverbrauch ist in Anbetracht der großen Zahl von Knoten und Kanten als gering
einzustufen.
83
8
Ausblick
Die Anreicherungen des TGraphen, die im Rahmen dieser Arbeit vorgenommen wurden,
ermöglichen bereits die Generierung von Sichtbarkeitsconstraints für die meisten in der Praxis
vorkommenden Situationen. Um alle wesentlichen Aspekte des Problembereichs im Rahmen dieser
Arbeit behandeln zu können, wurden einige Anreicherungen jedoch unter Einschränkungen
implementiert, so dass durch sie nur die in der Praxis häufigsten Konstellationen abgedeckt sind.
Für eine vollständige Umsetzung der Generierung von Sichtbarkeitsconstraints auf der Grundlage
von TGraphen sind somit noch einige Implementierungsarbeiten erforderlich, die im Kapitel
8.1. Notwendige Implementierungsarbeiten zusammengefasst werden. Über die
Sichtbarkeitsconstraints hinaus war es Ziel dieser Arbeit, die generelle Eignung von TGraphen für
den constraintbasierten Refaktorisierungsansatz zu zeigen. Das Kapitel 8.2. Über die
Sichtbarkeitsconstraints hinaus zeigt hierzu einige Möglichkeiten auf.
8.1
8.1.1
Notwendige Implementierungsarbeiten
Neu-Implementierung der statischen Bindungen
Statische Bindungen werden derzeit durch den GraBaJa-Faktenextraktor in den TGraphen
eingebracht. Die im Kapitel 4.5. Repräsentation statischer Bindungen durchgeführten
Untersuchungen haben gezeigt, dass diese Bindungen größtenteils zuverlässig ermittelt werden, im
Einzelfall aber auch von der Bindung gemäß [JLS3] § 15.12 abweichen. Dort wurde zudem bereits
im Abschnitt 4.5.3. Ermittlung statischer Bindungen nach Faktenextraktion ausgeführt, dass selbst
eine Korrektur des Faktenextraktors nicht das Problem lösen würde, dass statische Bindungen auch
für Deklarationen und Referenzen ermittelt werden müssen, die nach der Faktenextraktion in den
TGraphen eingebracht werden.
Um TGraphen produktiv als Fakten-Basis für Refaktorisierungs-Werkzeuge nutzen zu können, wäre
es demnach erforderlich, die statischen Bindungen von Methoden-Aufrufen und Feld-Zugriffen
außerhalb des Faktenextraktors und ausschließlich auf der Basis der im TGraphen repräsentierten
Information neu zu implementieren. Der GraBaJa-Faktenextraktor sollte ausschließlich zur
Extraktion der syntaktischen Quelltext-Fakten dienen. Jegliche Anreicherung um abgeleitete
Informationen sollte allein auf der Basis der im TGraphen zur Verfügung stehenden Information
erfolgen, um auch nachträgliche Änderungen des TGraphen neu analysieren und die Anreicherung
auf dieser Grundlage wieder vervollständigen zu können.
8.1.2
Vervollständigung der Referenzierung von Empfänger-Typen
Die Referenzierung von Empfänger-Typen erfolgt für Feldzugriffe und Konstruktor-Aufrufe
vollständig. Für Methoden-Aufrufe wurde hingegen eine Auswahl der am häufigsten
vorkommenden Aufruf-Formen für die Implementierung ausgewählt und im Abschnitt
5.5.1. Einschränkungen der umgesetzten Anreicherung beschrieben. Um die Referenzierung von
Empfänger-Typen zu vervollständigen, sind die verbleibenden Aufruf-Typen zu ergänzen.
84
8.1.3
Implementierung eines Bytecode-Faktenextraktors
Die im Rahmen dieser Arbeit für eine eingeschränkte Menge von Quelltext-Elementen umgesetzte
Bytecode-Faktenextraktion auf der Basis von BCEL berücksichtigt die öffentlichen Eigenschaften
der Deklarationen von Klassen und Interfaces, von Methoden (mit Modifiern, Rückgabetyp und
formaler Parameterliste) sowie von Variablen (mit Modifier und deklariertem Typ). Supertyp- und
Override-Beziehungen aus dem eigenen Quelltext-Bereich zu diesen Deklarationen werden
aufgebaut. Damit ist eine Grundlage für einen Bytecode-Faktenextraktor gelegt. Für eine produktive
Nutzung des Bytecode-Faktenextraktors müsste dieser um folgende Funktionen erweitert werden:
Repräsentation aller von außerhalb des Bytecodes sichtbarer Fakten. Insbesondere sind auch
Elemente mit Package-Zugriff erforderlich, da eigener Quelltext und Bytecode-Elemente
innerhalb desselben Packages deklariert sein können.
• Repräsentation der statischen Bindungen zu den aus dem Bytecode extrahierten
Deklarationen. Hierzu wäre die im Abschnitt 8.1.1. Neu-Implementierung der statischen
Bindungen geschilderte Implementierung des statischen Bindealgorithmus auf der Basis von
TGraphen erforderlich.
• Repräsentation der Referenzen, die aus dem Bytecode heraus auf Deklarationen innerhalb
des eigenen Quelltexts zeigen.
In diesem Kontext soll auf die Arbeit [WETZLER2011] hingewiesen werden, in welcher die
Implementierung eines Bytecode-Faktenextraktors auf Basis von BCEL beschrieben wird. Die dort
beschriebene Implementierung kann in Kombination mit der Implementierung der vorliegenden
Arbeit als Ausgangspunkt für einen Bytecode-Faktenextraktor auf TGraph-Basis dienen.
•
8.1.4
Der GraBaJa-Faktenextraktor und künftige Java-Versionen
Wie bereits in Abschnitt 7.2.2. Wartbarkeit des Faktenextraktors geschildert, hat sich der GraBaJaFaktenextraktor als schwer erweiterbar erwiesen. Für künftige Java-Versionen ist daher ggf. die
Implementierung eines neuen Faktenextraktors erforderlich.
8.2
8.2.1
Über die Sichtbarkeitsconstraints hinaus
Eignung für beliebige Constraint-Regeln
In dieser Arbeit wurde am Beispiel der Sichtbarkeitsconstraints gezeigt, wie TGraphen angepasst
werden können, um als geeignete Grundlage für constraintbasierte Refaktorisierungen zu dienen.
Die Konzentration auf Sichtbarkeitsconstraints soll jedoch nicht übersehen lassen, dass TGraphen
sich prinzipiell als Grundlage für die Umsetzung beliebiger Constraint-Regeln eignen.
Die gesamte im Rahmen dieser Arbeit angereicherte Information war entweder bereits im TGraphen
vorhanden oder ließ sich aus der Java-Sprachspezifikation ableiten. Ein erneuter Zugriff auf die
Quelltext-Dateien nach der durch den GraBaJa-Faktenextraktor erfolgten Generierung des
TGraphen war in keinem Fall notwendig. Die exemplarische Umsetzung für
Sichtbarkeitsconstraints hat gezeigt, dass die Anreicherung der Fakten-Basis in vielen Fällen
lediglich erfolgt, um auf die benötigte Information bei der späteren Generierung der ConstraintRegeln komfortabel und performant zugreifen zu können. Der im Kapitel 3.9. Vorgehensweise bei
85
Anpassungen im TGraphen beschriebene Workflow für die Aufnahme zusätzlicher Informationen
ist ebenso beliebige Informationen anwendbar, die für eine Umsetzung weiterer Constraint-Regeln
in die Fakten-Basis aufgenommen werden sollen. Vorstellbar sind beispielsweise
•
•
•
•
8.2.2
der Einsatz für die Generierung von Type Constraints, für den nach einem Abgleich der
Constraint-Regeln, die im Kapitel „2.4.2 Aufbau der Constraints“ in [BÄR2010] aufgeführt
werden, keinerlei zusätzliche Anpassungen der Fakten-Basis erforderlich sind, da sämtliche
dort beschriebenen Programmelemente und -konstrukte bereits im TGraphen repräsentiert
werden
die Aufnahme zusätzlicher Informationen für die Refaktorisierung paralleler Systeme wie
z. B. die Repräsentation der in [SCHÄFER2010-2] beschriebenen Dependency Edges in den
TGraphen
die Repräsentation programmrelevanter Information, die nicht in Java-Quelltexten vorliegt,
wie z. B. die Konfigurationsdateien von Dependency Injection Containern oder ViewDefinitionen von Web-Anwendungen, die als XHTML oder JSP-Dateien vorliegen
die Ergänzung von Analyse-Information externer Werkzeuge, wie dies z. B. im Abschnitt
2.3.3. Nutzung eines Java-Compilers beschrieben wurde.
Parallelen zur REFACOLA
In [STEI2011] wird auf der Basis des constraintbasierten Refaktorisierungsansatzes die
Beschreibungssprache REFACOLA (engl. Refactoring Constraint Language) zur Spezifikation von
Spracheigenschaften, Constraint-Regeln und Refaktorisierungen vorgestellt. Die Konzeption der
Sprache ist programmiersprachenübergreifend. Auch aus diesem Grund tun sich Parallelen zur
Verwendung von TGraphen auf, deren eingehendere Untersuchung lohnen würde. Hier soll
lediglich ein anschauliches Beispiel gegeben werden.
Die folgende Regeldefinition wurde mittels der REFACOLA notiert:
language Java
rules
FieldAccessibility
for all
d : Java.Field
r : Java.FieldReference
do
if
Java.binds(r, d)
then
r.hostType != d.hostType -> d.accessibility > private
end
Es fällt nicht schwer, diese Spezifikation in eine Implementierung für TGraphen umzusetzen. Die
eigentliche Regeldefinition kann innerhalb einer Methode erfolgen und hat erkennbare Ähnlichkeit
mit der oben gegebenen.
86
1 public class FieldAccessibility implements JavaRuleDefinition {
2
public void defineRule(Java5 tgraph) {
3
for (FieldAccess fa : findAllFieldAccesses(tgraph)) {
4
VariableDeclaration vd = getBoundVariableDeclaration(fa);
5
6
TypeSpecification type_vd = getTypeOfVariable(vd);
7
TypeSpecification type_fa = getReceiverTypeOfFieldAccess(fa);
8
9
if (type_vd != type_fa) {// Pruefung auf Identitaet ist hier moeglich.
10
checkAccessibilityOfVariable(vd);
11
}
12
}
13 }
14 // Die notwendigem Methoden-Deklarationen folgen.
15 }
Dass nichtsdestoweniger noch ein gutes Stück Weg zurückzulegen wäre, um beispielsweise
REFACOLA-Spezifikationen automatisiert in Quelltext wie diesen zu transformieren, zeigen die in
defineRule() verwendeten Methoden, die zusätzlich zu implementieren sind. Um einen
Eindruck von diesem Weg zu geben, werden hier Beispiel-Implementierungen dieser Methoden
gezeigt:
Zeile 2, Ermittlung aller Feld-Zugriffe:
private Set<FieldAccess> findAllFieldAccesses(Java5 tgraph) {
GreqlEvaluator eval = new GreqlEvaluator(
"from x:V{FieldAccess}" +
" reportSet x" +
" end", tgraph, null);
eval.startEvaluation();
Set<FieldAccess> access_set = new HashSet<FieldAccess>();
for (JValue res : (JValueSet)eval.getEvaluationResult()) {
access_set.add((FieldAccess)res.toObject());
}
return access_set;
}
Zeile 3, Ermittlung der Deklaration, an die ein Feld-Zugriff gebunden wird:
private VariableDeclaration getBoundVariableDeclaration(FieldAccess fa) {
IsDeclarationOfAccessedField idoaf =
fa.getFirstIsDeclarationOfAccessedFieldIncidence(EdgeDirection.OUT);
return (VariableDeclaration)idoaf.getThat();
}
87
Zeile 5, Ermittlung des Typs der Variablen-Deklaration:
private TypeSpecification getTypeOfVariable(VariableDeclaration vd) {
IsTypeOfVariable itov =
vd.getFirstIsTypeOfVariableIncidence(EdgeDirection.IN);
return (TypeSpecification)itov.getThat();
}
Zeile 6, Ermittlung des Empfänger-Typs des Feld-Zugriffs:
private TypeSpecification getReceiverTypeOfFieldAccess(FieldAccess fa) {
IsReceiverOfAccessedField iroaf =
fa.getFirstIsReceiverOfAccessedFieldIncidence(EdgeDirection.OUT);
return (TypeSpecification)iroaf.getThat();
}
Zeile 9, Prüfung der geforderten Sichtbarkeit:
private void checkAccessibilityOfVariable(VariableDeclaration vd) {
IsModifierOfVariable imov =
vd.getFirstIsModifierOfVariableIncidence(EdgeDirection.IN);
Modifier mod = (Modifier)imov.getThat();
if (mod.get_type().equals(Modifiers.PRIVATE)) {
// Constraintverletzung. Tue, was getan werden muss.
}
}
Bei einigen dieser Beispiele macht sich der bereits im Abschnitt 7.1.1.1. Methoden zum Navigieren
im TGraphen festgestellte Umstand bemerkbar, dass es an Schnittstellen-Methoden mangelt, die
unmittelbar von Knoten zu Knoten navigieren. Statt dessen ist es jeweils notwendig, zunächst die
verknüpfende Kante zu ermitteln und dann mittels getThat() und notwendiger Cast-Operation die
eigentlich interessierende Knoten-Instanz zu erlangen. Bei der ebenfalls möglichen Verwendung der
reachableVertices()-Methode hätte man statt dessen ein Set mit genau einem Knoten als
Ergebnis erhalten und jeweils den einen Knoten aus dem Set entnehmen müssen. Ohne diese
Umstände würde bereits rund die Hälfte des Beispiel-Quelltexts entfallen.
88
9
Zusammenfassung
Das Ziel dieser Arbeit war es, die Eignung von TGraphen als Fakten-Basis für den
constraintbasierten Refaktorisierungsansatz zu untersuchen.
Wie das Kapitel 2. Der constraintbasierte Refaktorisierungsansatz am Beispiel der
Sichtbarkeitsconstraints gezeigt hat, resultieren aus der Notwendigkeit einer effizienten ConstraintGenerierung hohe Ansprüche an die in der Fakten-Basis repräsentierten Informationen. Zum
Zwecke der Constraint-Generierung werden bestimmte Beziehungen wie etwa statische Bindungen,
Override-Beziehungen oder die Referenzierung von Empfänger-Typen besonders häufig benötigt
und müssen daher auf einfache und performante Weise ermittelbar sein. Dies erfordert nicht nur
eine vollständige Repräsentation aller (auch impliziter oder nur in Form von Bytecode vorliegender)
Programmelemente, sondern auch eine teils redundante Anreicherung der Fakten über die rein
syntaktische Ebene hinaus.
Im Kapitel 4. Identifikation der notwendigen Anpassungen wurde sichtbar, dass diese Ansprüche
von dem zur Verfügung stehenden Extraktor für Java-Fakten in vielerlei Hinsicht nicht erfüllt
werden. Dieser eignet sich zwar als zuverlässiges Transformationswerkzeug, um explizit
vorliegenden Java-Quelltext in eine TGraphen-Repräsentation zu überführen. Er ist jedoch weder in
der Lage, nur implizit oder in Form von Bytecode vorliegende Fakten zu ermitteln, noch erwies sich
die Auflösung von Methoden- und Feld-Bindungen als brauchbar. Weitere Beziehungen wie
Override-Beziehungen oder die Referenzierung von Empfänger-Typen sind gar nicht erst in einfach
zugreifbarer Form repräsentiert, sondern müssen vergleichsweise aufwändig aus den syntaktischen
Fakten und der Java Language Specification abgeleitet werden.
Andererseits wurde zunächst im Kapitel 3. TGraphen zur Repräsentation von Java-Fakten die
grundlegende Eignung von TGraphen für den gewünschten Einsatzzweck aufgezeigt. Die
Möglichkeiten zur Modellierung der Fakten-Basis sind auf Grund der Typisierung von Knoten und
Kanten sowie der Ergänzung durch Attribute umfangreich. Die so strukturierten Fakten können
mittels einer mächtigen Abfragesprache analysiert werden. Dabei gewähren zahlreiche
Schnittstellen-Methoden einen flexiblen Zugang zu der gewünschten Information.
Die vorgenommenen Implementierungen, die in den Kapiteln 5. Dokumentation der
vorgenommenen Anpassungen und 6. Exemplarische Constraint-Generierung mittels TGraph
dargestellt wurden, bestätigen diese Eignung. Implizite Quelltext-Elemente werden nach der
erweiterten Faktenextraktion ebenso wie Override-Beziehungen vollständig im TGraphen
repräsentiert. Wo die Implementierung unvollständig blieb, lag dies nicht an der Nutzung von
TGraphen als Fakten-Basis, sondern an der Komplexität der einzubringenden Information. So
erwies sich die Java Language Specification der statischen Bindungen oder der Ermittlung der
statischen Typen verschiedener Java-Ausdrücke als zu umfangreich, als dass eine Umsetzung aller
Einzelheiten sich in den Rahmen dieser Arbeit gefügt hätte. Auch die Entwicklung eines BytecodeFaktenextraktors konnte nur bis zu einem Punkt geführt werden, von dem aus sichtbar wird, dass
eine Umsetzung auf der Basis von TGraphen grundsätzlich in eleganter Form möglich ist.
Auch aus der Sicht des Entwicklers, der auf der Grundlage von TGraphen Entwicklungsarbeit zu
leisten hat, ergab sich im Rahmen des Kapitels 7. Diskussion ein weitgehend positives Bild. Auf der
Habenseite stehen ein nachvollziehbares und gut erweiterbares Metamodell, eine umfangreiche
Entwicklungs-Schnittstelle und die leistungsfähige integrierte Abfragesprache. Diesen stehen eine
anfänglich steile Lernkurve sowie kleinere Schwächen einzelner Schnittstellen-Methoden
89
gegenüber. Es wurden Vorschläge zur Ergänzung der Entwicklungs-Schnittstelle gemacht, die für
die speziellen Anforderungen der Analyse von Java-Elementen die Entwicklungsarbeit erleichtern
und die Qualität der entwickelten Software verbessern würden.
Im Rahmen des Kapitels 8. Ausblick wurden zunächst noch einmal die nächsten Schritte
zusammengefasst, die erforderlich wären, wenn der Ansatz weiter verfolgt werden soll. Darüber
hinaus wurde nach der vorangegangenen Konzentration auf zahlreiche Details die Perspektive
wieder auf die allgemeinere Problemstellung constraintbasierter Refaktorisierungen gelenkt und an
Hand einiger Beispiele aufgezeigt, dass die für Sichtbarkeitsconstraints gewonnenen Erkentnisse
sich auch auf weitere Constraint-Arten anwenden lassen.
Zusammenfassend lässt sich feststellen, dass die Nutzung von TGraphen als Fakten-Basis für die
Entwicklung constraintbasierter Refaktorisierungs-Werkzeuge eine vielversprechende, wenn auch
nicht kurzfristig erreichbare Möglichkeit darstellt.
90
10
10.1
Anhang
Installations- und Build-Hinweise zu JGraLab und GraBaJa
Die folgenden Installationshinweise beziehen sich auf die im Subversion-Repository des
Lehrgebiets Steimann sowie auf der Installations-CD zu dieser Arbeit abgelegten Projekte. JGraLab
und GraBaJa werden in separaten Eclipse-Java-Projekten gepflegt. Des Weiteren ist für den Build
der Projekte ein zusätzliches Projekt namens common notwendig, welches verwendete Bibliotheken
bereitstellt. Im Subversion-Repository stellt sich dies wie folgt dar.
Abbildung 10: Entwicklungsprojekte
Die Projekte können aus dem Repository mittels Check Out (Rechtsklick auf das jeweilige Projekt
und dann Check Out wählen) in den Eclipse-Workspace aufgenommen werden. Die Installation von
der CD erfolgt über die Funktion File → Import → Existing Projects into Workspace.
Abbildung 11: Import des Projekts
Dabei sollte die Option Copy projects into workspace aktiviert werden. Im nachfolgenden Dialog
wählt man als root directory das jeweilige Projektverzeichnis. Im Anschluss an den Import sollte
zudem überprüft werden, ob die Projektordner mit einem Schreibschutz versehen wurden, was bei
einem Import von CD der Fall sein kann, da ggf. die Schreibschutz-Attribute der Dateien auf der
CD von Eclipse übernommen werden. Am erreicht man dies, indem in den Eigenschaften des
Workspace-Ordners (im Windows-Explorer Rechtsklick auf den Ordner, dann auf Eigenschaften
klicken) das Attribut Schreibgeschützt deaktiviert wird.
91
Die drei Projekte müssen im Dateisystem auf einer Ebene liegen und sind in der Reihenfolge
(1) common, (2) jgralab und (3) grabaja zu bauen. Zu diesem Zweck ist in jedem der Projekte
eine build.xml-Datei für einen Ant-Build abgelegt. Der Build von jgralab und grabaja
erzeugt jeweils eine .jar-Datei unter <Projektname>/build/jar. Diese können ins eigene
Entwicklungsprojekt eingebunden werden (siehe Abschnitt 10.2.2. Installation).
Für JGraLab existiert im Wiki des IST eine Installationsanleitung unter
[URL:JGRALABINSTALL].
10.2
Dokumentation des Erweiterungs-Projekts
10.2.1
Struktur des Projekts
Das Erweiterungs-Projekt wurde unter dem Namen TGraphEnhancements in das SubversionRepository des Lehrgebiets Programmiersysteme der Fernuniversität Hagen sowie auf die
Installations-CD dieser Arbeit aufgenommen. Die folgende Abbildung gibt eine Übersicht der
wesentlichen Implementierungs-Bestandteile:
Abbildung 12: Übersicht wesentlicher
Implementierungs-Bestandteile
92
Im Package demo finden sich Dateien und Verzeichnisse, die im Abschnitt 10.2.4. Demonstration
mittels TGraph-Browser näher beschrieben werden.
Das Package enhance enthält alle Quelltexte, welche im Rahmen der Anreicherung des TGraphen
relevant sind. Der Controller im enhance.ablauf-Package steuert die Reihenfolge der
Anreicherung. Im enhance.handlers-Package sind die Klassen enthalten, welche die
eigentlichen Modifikationen im TGraphen ausführen. Für jede Art der Informationsanreicherung
wurde eine Handler-Klasse implementiert. Struktur und Ablauf der Handler-Klassen sind in den
meisten Fällen sehr ähnlich. Die Faktenextraktion aus Bytecode wurde im Package enhance.bcel
umgesetzt. Es ist sowohl möglich, einzelne .class-Dateien als auch ganze .jar-Dateien zu
übergeben. Im enhance.dao-Package wurden Abfragen an den TGraphen versammelt, die
entweder GReQL nutzen oder auf dem Einsatz von Schnittstellen-Methoden basieren. Auf diese
Weise sollte nach Möglichkeit die eigentliche Anpassungslogik von den Abfragen separiert werden.
Da für einige Knotentypen besonders viele Abfragen entstanden sind, wurden für diese separate
DAO-Klassen erstellt. Beispielsweise enthält das ClassDefinitionDao ausschließlich Abfragen,
die Klassen-Definitionen zurückliefern. Im enhance.service-Package wurden einige häufig
verwendete Hilfsmethoden implementiert.
Im Package examples wurden die Beispiel-Implementierungen für die Constraint-Generierung
sowie für die Parallelen zur REFACOLA abgelegt.
Das Package testsources enthält zahlreiche Beispiel-Quelltexte, die im Lauf der
Untersuchungen der Arbeit erstellt wurden, um die Repräsenation bestimmter Programmkonstrukte
im TGraphen überprüfen zu können. Diese Quelltexte werden auch von den Unit-Tests des
Packages test genutzt. Zu jeder implementierten Handler-Klasse sowie zum BytecodeFaktenextraktor existiert eine entsprechende Unit-Test-Klasse, welche die vorgenommenen
Anreicherungen testet.
Über die Packages hinaus befindet sich im Verzeichnis jar die BCEL-Bibliothek bcel-5.2.jar
sowie die Datei BcelTestclass.jar. Letztere wird von der Testklasse eingelesen, welche die
Bytecode-Faktenextraktion überprüft. Im Verzeichnis doc/literatur befinden sich zahlreiche
der im Literaturverzeichnis dieser Arbeit referenzierten Dokumente mit den dort verwendeten
Kurzbezeichnungen.
10.2.2
Installation
Die Integration des TGraphEnhancements-Projekts in den Eclipse-Workspace erfolgt wie bereits
in 10.1. Installations- und Build-Hinweise zu JGraLab und GraBaJa beschrieben. Ein Build ist nicht
erforderlich.
Nach dem Build von JGraLab und GraBaJa sind deren erzeugte .jar-Dateien (unter
jgralab/build/jar und grabaja/build/jar) sowie die BCEL-Bibliothek bcel-5.2.jar
in den Build Path des TgraphEnhancements-Projekts aufzunehmen. Dies erfolgt im Dialog Java
Build Path in den Properties des Projekts mittels Add External JARs. Es sollte sich das folgende
Bild ergeben:
93
Abbildung 13: Konfiguration des Build Path
Danach sollten sich die unter src/test abgelegten Unit-Tests fehlerfrei mit dem Test runner
JUnit 4 ausführen lassen.
10.2.3
Zahlen zum Umfang der Implementierung
Mittels des Metrics-Plugins für Eclipse (siehe [URL:METRICS]) wurden einige Kennzahlen der
Umsetzung ermittelt. Es wurden insgesamt 4931 Lines of Code (LoC) bzw. 3028 Method Lines of
Code (MLoC) in 86 Klassen mit 51 Attributen und 236 Methoden implementiert. Davon entfielen
1827 LoC und 939 MLoC auf den Testcode der Packages test und testsources. Auf den
eigentlichen Code zur Anpassung des TGraphen entfielen 2846 LoC und 1922 MLoC.
10.2.4
Demonstration mittels TGraph-Browser
Das Graphenlabor stellt eine Webanwendung für das interaktive Navigieren in TGraphen zur
Verfügung, welche nun vorgestellt werden soll, um sie für die Ansicht einiger DemonstrationsBeispiele nutzen zu können. Hierfür ist zunächst der Server zu starten. Dies geschieht am
einfachsten innerhalb von Eclipse über die Ausführung der Java-Klasse
de.uni_koblenz.jgralab.utilities.tgraphbrowser.TGraphBrowserServer aus dem
JGraLab-Projekt, die über eine main()-Methode verfügt. Um die graphische Visualisierung nutzen
zu können, müssen zwei Programm-Argumente in der Run-Konfiguration eingetragen werden:
Abbildung 14: Argumente für den Start des TGraphBrowserServers
94
•
•
Mittels der -d-Option ist der Pfad zu der ausführbaren Datei dot.exe anzugeben, die mit
der Software Graphviz (siehe [URL:GRAPHVIZ]) installiert wird. Diese wird vom
TGraphBrowserServer zur Generierung SVG-Dateien (siehe [URL:SVG]) genutzt, die in
einem Webbrowser angezeigt werden können.
Die -dt-Option gibt an, wie lange der TGraphBrowserServer maximal auf die
Generierung der SVG-Dateien warten soll. Ohne die Angabe dieser Option funktioniert die
Graphenvisualisierung nicht.
Beim Ausführen des TGraphBrowserServers sollte es in der Konsole zu einer Ausgabe wie der
folgenden kommen:
TGraphBrowserServer is running on port 8080
The current workspace is:
C:\Users\Besitzer\AppData\Local\Temp\tgraphbrowser\workspace
Press CTRL + C to quit.
Die Anwendung kann jetzt in einem Webbrowser unter http://localhost:8080 aufgerufen
werden. Es öffnet sich die folgende Startseite:
Abbildung 15: Startseite des TGraph-Browsers
Über den Durchsuchen-Button können nun TGraphen-Dateien in den Server geladen werden. Zur
Demonstration der vorgenommenen Anreicherungen liegen hierzu einige bereits generierte Dateien
vor. Unterhalb des Packages demo finden sich mehrere Sub-Packages, die für mehrere
Erweiterungs-Themen Beispiel-Quelltexte enthalten. Unterhalb dieser Sub-Packages findet sich
jeweils ein Package generated, welches die TGraphen-Dateien enthält, die in den Server geladen
werden können. Eine der Dateien heißt *_erweitert.tg. Diese enthält den TGraphen nach
Anreicherung, die andere den ummodifierten TGraphen, wie er vom GraBaJa-Faktenextraktor
erzeugt wird. Ein Beispiel:
95
Abbildung 16: Struktur der
Demonstrations-Beispiele
Nach dem Hochladen der .tg-Datei dauert es eine Weile, bis der TGraph in den Server
eingelesen ist. In dieser Zeit wird der folgende Dialog angezeigt:
Abbildung 17: Anzeige während des
TGraphen-Uploads
Ist der Graph geladen, so wird eine Seite wie die folgende angezeigt:
Abbildung 18: Tabellen-Ansicht des eingelesenen TGraphen
Dabei handelt es sich zunächst um die Tabellen-Ansicht des eingelesenen TGraphen. Es würde an
dieser Stelle zu weit führen, alle interessanten Details zur Verwendung des TGraph-Browsers
auszuführen. Die Interaktion ist weitgehend selbsterklärend, bei Bedarf kann [JANKE2010]
konsultiert werden. Auf folgende Möglichkeiten sei kurz hingewiesen:
•
•
•
Im Explorer-Bereich links kann mittels der Typ-Hierarchie des TGraphen separat nach
Knoten- und Kantentypen gebrowst werden.
In der Tabellen-Ansicht kann auf sowohl geblättert als auch auf Knoten- und Kantentypen
geklickt werden, um durch den Graphen zu navigieren.
Durch Klick auf den Button „Select by GReQL“ kann ein Editor-Fenster geöffnet werden,
um eine GReQL-Abfrage an den Graphen zu stellen.
Von näherem Interesse ist der Button „Show graphical view“. Klickt man auf diesen, so wechselt
der TGraph-Browser von der Tabellen-Ansicht in den Visualisierungs-Modus.
96
Abbildung 19: Visualisierungs-Modus des TGraph-Browsers
Zur Navigation können hier einige Zoom-Elemente links verwendet werden. Es ist auch möglich,
auf einzelne Knoten des Graphen zu klicken, die dann farbig markiert werden. Setzt man danach in
der Kopfleiste die „Size of environment“, so kann die nähere Umgebung des markierten Knoten
eingegrenzt oder erweitert werden.
Es wurden Demonstrations-Beispiele zu folgenden Themen erstellt:
• Override-Beziehung: Hier können im erweiterten TGraphen die IsOverriddenMethodByund IsOverridingMethodOf-Kanten gefunden werden.
•
•
Implizite Modifier: In diesem Beispiel werden bei der Erweiterung einige zusätzliche
Kanten zu Modifiern in den TGraphen aufgenommen, deren implicit-Attribut den Wert
true hat. Um diese Attribut zu sehen, kann im TGraph-Browser die Ansicht der Attribute
über „Show attributes“ aktiviert werden.
Receiver-Typen: Dieses Beispiel zeigt die Anreicherung der IsReceiverOf-Kanten.
Sollen eigene Beispiele erzeugt werden, so wurde hierfür ein Parser implementiert. Im Package
demo.generierung des TGraphEnhancements-Projekts findet sich neben der Klasse
DemoParser die Datei files2parse.txt. In diese Datei können beliebig viele Zeilen mit
Dateinamen (entweder relativ zum Projekt oder absolut) eingetragen werden. Wird ein Verzeichnis
angegeben, so wird in diesem Verzeichnis und allen seinen Unterverzeichnissen nach Dateien
gesucht, die auf .java enden. Führt man nach der gewünschten Eintragung der zu parsenden
Dateien und Verzeichnisse den DemoParser aus, so werden folgende vier Dateien im outputVerzeichnis des TgraphEnhancements-Projekts erzeugt oder überschrieben:
97
Abbildung 20: Generierte
Dateien
Zusätzlich zu den TGraphen-Dateien, die auf .tg enden, werden Graphen-Dateien im DOT-Format
erzeugt. Diese können bei Bedarf mit Graphviz geladen werden, um beispielsweise BitmapGrafiken für die Graphen generieren zu können.
10.3
Versionsinformation
Als Basis der Implementierungsarbeit, die im vorliegenden Dokument beschrieben wird, wurden
die folgenden Revisionsnummern der jeweiligen damaligen Subversion-Projekte des IST
verwendet:
•
•
•
common: Revisionsnummer 3319 vom 14.11.2010.
jgralab: Revisionsnummer 3373 vom 14.11.2010.
grabaja: Revisionsnummer 12989 vom 14.11.2010.
Die Implementierungsarbeit erfolgte unter Eclipse Helios (Version 3.6.1) unter Verwendung von
Java 1.6.
98
Literaturverzeichnis
ALUR2002:
BÄR2010
BALD2008:
BILD2006:
CHEN1990:
DAHM1998:
EBERT1995:
EBERT1996:
EBERT2002:
EBERT2010:
EKMAN2008:
FOWLER1999:
FUHR2011:
GREQLREF:
GUPRO1998:
HOF2007:
HORN2008:
HORN2011:
JANKE2010
Alur, Deepak; Crupi, John; Malks, Dan, Core J2EE Patterns – Die
besten Praxislösungen und Design-Strategien, 2002
Bär, Robert, Mutantengenerierung durch Type Constraints, 2010
Baldauf, Arne; Vika, Nicolas, Java-Faktenextraktor für Gupro, 2008
Bildhauer, Daniel, Ein Interpreter für GReQL 2 - Entwurf und
prototypische Implementation, 2006
Y.-F. Chen; M. Y. Nishimoto; C. V. Ramamoorthy, Tutorial on the C
Information Abstraction System, 1990
Dahm, Peter; Widmann, Friedbert, Das Graphenlabor, 1998
Ebert, Jürgen; Franzke, Angelika, A Declarative Approach to Graph
Based Modeling, in: Mayr, E.; Schmidt, G.; Tinhofer, G.,
Graphtheoretic Concepts in Computer Science, 1995
Ebert, Jürgen; Gimnich, Rainer; Winter, Andreas,
Wartungsunterstützung in heterogenen Sprachumgebungen. Ein
Überblick zum Projekt GUPRO, 1996
Ebert, Jürgen; Kullbach, Bernt; Riediger, Volker; Winter, Andreas,
GUPRO – Generic Understanding of Programs, 2002
Ebert, Jürgen; Bildhauer, Daniel, Reverse Engineering Using Graph
Queries, 2010
Ekman, Torbjörn; Schäfer, Max; Verbaere, Mathieu, Refactoring is not
(yet) about transformation, 2008
Fowler, Martin, Refactoring. Improving the Design of Existing Code,
1999
Fuhr, Andreas, Identifying Legacy Code for Service Implementation
using Dynamic Analysis and Data Mining, 2011
Bildhauer, Daniel; Großmann, Eckhard; Horn, Tassilo, GReQLReference Card, 2010
Ebert, Jürgen (Herausgeber); Gimnich, Rainer (Herausgeber); Stasch,
Hans H. (Herausgeber); Winter, Andreas (Herausgeber), GUPRO:
Generische Umgebung zum Programmverstehen, 1998
Hofstedt, Petra; Wolf, Armin, Einführung in die ConstraintProgrammierung. Grundlagen, Methoden, Sprachen, Anwendungen,
2007
Horn, Tassilo, Ein Optimierer für GReQL 2,
Fuhr, Andreas; Horn, Tassilo; Riediger, Volker, An Integrated Tool
Suite for Model-Driven Software Migration towards Service-Oriented
Architectures, 2011
Janke, Daniel Dominik, TGraphBrowser - Implementierung eines
Webservers zum Browsen von TGraphen, 2010
99
JARZ1995:
S. Jarzabek; T. P. Keam, Design of a Generic Reverse Engineering
Assistant Tool, 1995
JLS3:
Gosling, James; Joy, Bill; Steele, Guy; Bracha, Gilad, The Java
Language Specification Third Edition, 2005
KAHLE2006:
Kahle, Steffen, JGraLab: Konzeption, Entwurf und Implementierung
einer Java-Klassenbibliothek für TGraphen, 2006
KAMP1996:
Kamp, Manfred, GReQL - eine Anfragesprache für das GUPRORepository 1.1, 1996
KEG2007:
Kegel, H., Contraint-basierte Typinferenz für Java 5, 2007
MAR2006:
Marchewka, Karin, GReQL 2, 2006
MENS2004
Mens, Tom; Tourwé, Tom, A Survey of Software Refactoring, 2004
PAS2011:
Pasenkova, Anastasia, Ein EclipsePlugin zum Aufspüren toter
Codefragmente auf Basis von Sichtbarkeitsconstraints, 2011
PEREZ2009:
Pérez, Javier; Crespo, Yania; Hoffmann, Berthold; Mens, Tom, A
Case Study to Evaluate the Suitability of Graph Transformation tools
for Program Refactoring, 2009
RENSINK2009:
Rensink, Arend; Zambon, Eduardo, A Type Graph Model for Java
Programs, 2009
SCHÄFER2010:
Schäfer, Max; de Moor, Oege, Specifying and Implementing
Refactorings, 2010
STEI2009:
Steimann, Friedrich; Thies, Andreas, From Public to Private to
Absent: Refactoring Java Programs under Constrained Accessibility,
2009
STEI2010:
Steimann, Friedrich, Korrekte Refaktorisierungen: Der Bau von
Refaktorisierungswerkzeugen als eigenständige Disziplin, 2010
STEI2011:
Steimann, Friedrich; Kollee, Christian; von Pilgrim, Jens, A
Refactoring Constraint Language and its Application, 2011
TIP2003:
Tip, Frank; Kiezun, Adam; Bäumer, Dirk, Refactoring for
generalization using type constraints, 2003
TIP2007:
Tip, Frank, Refactoring Using Type Constraints, 2007
URL:ANTLRGRAMMAR: http://www.antlr.org/wiki/display/ANTLR3/Grammars
URL:ASTREWRITER:
http://help.eclipse.org/helios/index.jsp?topic=/org.eclipse.jdt.doc.isv/
reference/api/ org/eclipse/jdt/core/dom/rewrite/ASTRewrite.html
URL:BCEL:
http://jakarta.apache.org/bcel/
URL:COBUS:
http://www.uni-koblenz-landau.de/koblenz/fb4/institute/
IST/AGEbert/projekte/cobus
URL:DOT
http://www.graphviz.org/doc/info/lang.html
URL:EPL:
http://www.eclipse.org/legal/epl-v10.html
URL:GPL3:
http://www.gnu.org/licenses/gpl-3.0.html
URL:GRAL:
http://www.uni-koblenz-landau.de/koblenz/fb4/institute/
IST/AGEbert/MainResearch/Graphentechnologie/EER-GRAL
URL:GRAPHVIZ:
http://www.graphviz.org/
100
URL:GREQL:
http://www.uni-koblenz-landau.de/koblenz/fb4/institute/
IST/AGEbert/MainResearch/Graphentechnologie/GReQL
URL:GUPRO:
http://www.uni-koblenz-landau.de/koblenz/fb4/institute/
IST/AGEbert/projekte/abgprojekte/GUPRO
URL:HELENA:
https://helena.uni-koblenz.de/redmine
URL:ISOURCEREF:
http://help.eclipse.org/helios/index.jsp?topic=/org.eclipse.cdt.doc.isv/
reference/api/org/eclipse/cdt/core/model/ISourceReference.html
URL:ISTGRAPHEN:
http://www.uni-koblenz-landau.de/koblenz/fb4/institute/
IST/AGEbert/MainResearch/Graphentechnologie
URL:ISTWIKI:
http://userpages.uni-koblenz.de/~ist/Main_Page
URL:JCONSOLE:
http://download.oracle.com/javase/6/docs/
technotes/guides/management/jconsole.html
URL:JDT:
http://www.eclipse.org/jdt
URL:JGRALABDOWN:
http://userpages.uni-koblenz.de/~ist/JGraLab_Download
URL:JPROFILER:
http://www.ej-technologies.com
URL:JGRALABINSTALL: http://userpages.uni-koblenz.de/~ist/JGraLab_Documentation
URL:JTRANSFORMER: https://sewiki.iai.uni-bonn.de/research/jtransformer/start
URL:JUSTADDJ
http://jastadd.org/web
URL:METRICS
http://metrics.sourceforge.net/
URL:REDSEEDS:
http://www.redseeds.eu/
URL:SOAMIG:
http://www.soamig.de/
URL:SVG
http://www.w3.org/TR/SVG11/
URL:Z:
http://www.imn.htwk-leipzig.de/~weicker/pmwiki/pmwiki.php/Main/
SpezifikationsspracheZ
WELLS1995:
C. H. Wells, R. Brand; L. Markosian, Customized Tools for Software
Quality Assurance and Reengineering, 1995
WETZLER2011
Wetzler, Gabriel, Faktenextraktion aus Java-Bytecode für
Refaktorisierungswerkzeuge, 2011
WINTER2000:
Winter, Andreas, Referenz-Metaschema für visuelle
Modellierungssprachen, 2000
ZIM2009:
Zimmermann, Yvonne; Uhlig, Denis; Kaiser, Uwe, Tool- und
Schnittstellenarchitektur für eine SOA-Migration, 2009
101
Herunterladen