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