Tag Archive for: Java

Extraktion von Software-Metriken aus Java-Dateien mit ANTLR4

In der Software-Entwicklung wird mehr und mehr auf Metriken gesetzt, um den Entwicklungsprozess zu messen und zu verbessern. Tools wie SonarQube und FindBugs helfen dabei – doch sie haben ihre Grenzen. Sie brauchen build-fähige Projekte. Die Metriken sind manchmal nicht genau genug dokumentiert oder lassen sich nur schwer anpassen.

Dabei ist es gar nicht so schwierig, ein eigenes Tool zu schreiben. So können die Metriken selbst definiert, auf Textdateien angewendet, als Skript automatisiert und an beliebige Schnittstellen weitergeben werden – z. B. als CSV-Datei nach R, per Webinterface oder als E-Mail.

Im Folgenden wird gezeigt, wie mit ANTLR4 aus Java-Textdateien Metriken erhoben werden können. Das Beispiel-Projekt steht auf GitHub zur Verfügung.

Statische Code-Analyse mit ANTLR4

ANTLR4 (ANother Tool for Language Recognition) wird seit 1989 von Terence Parr, Professor an der University of San Francisco, entwickelt. Das Tool selbst ist kostenlos; die ausführliche Dokumentation kostet jedoch 27 US-Dollar. Für den Einstieg reicht die kostenlose Dokumentation. ANTLR4 ist eine Java-Bibliothek.

Beim Parsen erstellt ANTLR4 auf Basis einer Grammatik einen Syntaxbaum. Nach dem Parsen wird der Syntaxbaum mit einem Walker abgelaufen. Dabei auftretende Events können über ein Listener-Interface abgefangen werden. Durch das Verarbeiten der Events können eigene Metriken berechnet werden.

Das Listener-Interface selbst wird über die Grammatik spezifiziert. ANTLR4 generiert aus der Grammatik Listener in Java, von denen anschließend geerbt wird.

Für viele Programmiersprachen gibt es bereits fertige Grammatiken, die auf GitHub gemeinsam entwickelt werden (siehe z. B. Java-Grammatik). Ansonsten muss die Grammatik selbst geschrieben werden.

Das Beispiel-Projekt

Im Beispiel wird der Klassenname und die Anzahl von Methoden aus einer Java-Klasse erhoben:

/**
 * A cat is a cat is a cat.
 *
 */

public class Cat {

      private String name;

      private int height;

      public Cat(String name, int height) {

            setName(name);

            setHeight(height);

      }

      public String getName() {

            return name;

      }

      public void setName(String name) {

            this.name = name;

      }

      public int getHeight() {

            return height;

      }

      public void setHeight(int height) {

            this.height = height;

      }
}

Das Ergebnis wird in einer Map<String, Integer> ausgegeben.

Vorbereitungen

Im Beispiel werden Eclipse, ein ANTLR4-Eclipse-Plugin und Maven benutzt. ANTLR4 lässt sich auch unabhängig von Eclipse und Maven benutzen.

Nach der Installation des Eclipse-Plugins muss in der pom.xml eine Dependency ergänzt werden:

<dependency>

<groupId>org.antlr</groupId>

<artifactId>antlr4-maven-plugin</artifactId>

<version>4.5</version>

</dependency>

Die Java-Grammatik wird von GitHub heruntergeladen und unter src/main/antlr4/Java.g4 abgelegt. Anschließend werden per Rechtsklick auf Java.g4 -> Run As -> Generate ANTLR Recognizer die Hilfsdateien von ANTLR4 angelegt.

Damit die Hilfsdateien von Eclipse als Java-Quellcode erkannt werden, werden sie nach src/main/java/generated kopiert.

Metriken definieren

ANTLR4 erstellt aus der Grammatik einen JavaBaseListener. Zum Definieren der Metriken wird ein eigener Listener programmiert, der vom JavaBaseListener erbt. Wenn ANTLR4 später den Syntaxbaum durchläuft, ruft es die entsprechenden Listener-Methoden auf.

/**
* Listens to Events, if ANTLR walks a parse tree.
*
* @author Sven Meyer
*/

public class Listener extends JavaBaseListener {

Im JavaBaseListener sind alle Events, auf die gehört werden kann, als Methode aufgeführt. Um nun den Klassennamen für die Metrik zu erfahren, wird das Event ClassDeclaration abgehört:

/** Listen to matches of classDeclaration */

@Override

public void enterClassDeclaration(JavaParser.ClassDeclarationContext ctx) {

setClazzName(ctx.Identifier().toString());

}

Um die Anzahl der Methoden zu erhalten, wird ein Counter bei jedem Auftreten einer MethodDeclaration erhöht.

/** Listen to matches of methodDeclaration */

@Override

public void enterMethodDeclaration(JavaParser.MethodDeclarationContext ctx) {

methodCount++;

}

ANTLR ausführen

Damit ANTLR ausgeführt werden kann, braucht es noch die zu lesende Datei und den Listener. Dazu sind nur wenige Zeilen Code notwendig:

public Map<String, Integer> extract(File importFile) throws Exception {

/** Prepare input for ANTLR. */

InputStream is = new FileInputStream(importFile.getAbsolutePath());

ANTLRInputStream input = new ANTLRInputStream(is);

JavaLexer lexer = new JavaLexer(input);

CommonTokenStream tokens = new CommonTokenStream(lexer);

JavaParser parser = new JavaParser(tokens);



/** Parse. */

ParseTree tree = parser.compilationUnit();



/** Create standard walker. */

ParseTreeWalker walker = new ParseTreeWalker();



/** Initiate walk of tree with listener. */

Listener listener = new Listener(parser);

walker.walk(listener, tree);



/** Save the results. */

Map<String, Integer> result = new HashMap<>();

result.put(listener.getClazzName(), listener.getMethodCount());

Ein Test prüft, ob der Klassenname und die Methodenanzahl richtig extrahiert werden:

@Test

public void test() throws Exception {

/** Prepare. */

// String = clazzName, Integer = methodCount

Map<String, Integer> result = new HashMap<>();



/** Execute. */

Extractor e = new Extractor();

result = e.extract(new File("src/main/resources/Cat.java"));



/** Test. */

// clazzName should be Cat

assertTrue(result.keySet().contains("Cat"));

// count of methods should be 4

assertEquals(new Integer(4), result.get("Cat"));

}

Die fertige Projektstruktur sieht in Eclipse so aus:

01_projektstruktur

Abbildung 1: Die fertige Projektstruktur.

Nun kann der Test erfolgreich durchgeführt werden.

02_erfolgreicher_test

Abbildung 2: Der Testfall läuft erfolgreich.

 Fazit

Damit der Code produktiv genutzt werden kann, sollten noch mögliche I/O- und ANTLR-Fehler behandelt werden. Außerdem müssen die erhobenen Daten weitergegeben werden, um z. B. als CSV-Datei in R importiert werden zu können.

Erfahrungsgemäß ergeben sich aus dem großen Sprachumfang viele Sonderfälle, die bei der Definition von Metriken berücksichtigt werden müssen. Hier helfen Testfälle weiter, um eine korrekte Messung sicherzustellen.

Das Beispiel konnte zeigen, dass leicht eigene Metriken aus Java-Quellcode erhoben werden können. So werden Quellcode und strukturierte Texte für die Analyse und Optimierung zugänglich.

 

Referenzen & Links

  • ANTLR4
    Die Projektseite von ANTLR4.
  • Java-Grammatik
    Die ANTLR4-Grammatik für die Java-Programmiersprache.
  • Beispielprojekt auf GitHub
    Das in diesem Tutorial verwendete Beispielprojekt auf GitHub.
  • ANTLR4-Eclipse-Plugin
    Das Eclipse-Plugin, das zum Ausführen von ANTLR4 in Eclipse benutzt werden kann.
  • Rascal MPL
    Eine Meta-Programmiersprache. Erlaubt nicht nur die Analyse, sondern auch das Erzeugen + Verändern von Quellcode.
  • Fallstudie Java-Korpus
    Eine laufende Fallstudie des Autors, in der ANTLR4 benutzt wird, um die typische Verwendung von Java-Sprachkonstrukten in 2,8 Mio. Dateien zu untersuchen.