Builder, Natures, Marker und Quick Fixes

Aus Eclipse
Wechseln zu: Navigation, Suche

"Nichts ist praktischer, als eine gute Theorie." (T. Karman et al.)

Präambel

Die in diesem Artikel beschriebenen Funktionen von Eclipse beziehen sich,auf die Version 3.5.2 des Eclipse SDK.

Konzepte & Modell

Das Komponenten-Ökosystem

Builder haben die Aufgabe aus den Quellcode-Dateien ausführbare Dateien zu erzeugen. Das Java-Plug-In von Eclipse verwendet einen Inkrementellen Builder der immer nur die Quellcode-Dateien neu kompiliert die sich geändert haben. Marker werden z.B. verwendet um die genaue Position (Zeile im Quellcode) im Quellcode-Editor zu markieren an der ein Compiler-Fehler aufgetreten ist, diese treten also visuell in Erscheinung. Die Verwendung von Markern beschränkt sich allerdings nicht nur auf Code-Dateien, sie lassen sich auf beliebige Dateien anwenden die sich mit dem Editor darstellen lassen. Weiters können damit nicht nur Compiler-Fehler sondern auch Tasks und Warnungen signalisiert werden. Natures bezeichnen die "Natur", oder eine bestimmte Charakteristik eines Projektes, z.B. kann ein Projekt eine "Java"-Nature haben. Ein Builder wiederum wird derart konfiguriert dass er nur auf Projekte einer gewissen Nature angewendet wird. Auf diese Art wird ein bestimmter Builder mit einem Projekt verknüpft. Quick Fixes sind Lösungsvorschläge für einen Marker eines bestimmten Typs bzw. Problems, die vom Benutzer explizit ausgewählt werden müssen. Quick Fixes nehmen definierten Änderungen an einer Datei vor. Ein Beispiel des Java Plugins ist das automatisches Einfügen eines "import" Statements für ein fehlendes Package). Auch sie treten visuell in Erscheinung. Quick-Fixes sind zwar nicht direkt dem Build-System zuzuordnen, werden aber genau dort oft eingesetzt.


Zusammenhänge

Das Plug-In-Manifest

Ein Eclipse Plug-In besteht immer aus der Implementierung in Form von Java-Classfiles und einer Deklaration - dem Plugin-Manifest in der Datei plugin.xml. Hier machen auch die hier beschriebenen Komponenten Builder,Natures,Marker und Quickfixes keine Ausnahme. Um diese Komponenten in das Eclipse Framework einzubinden sind jeweils entsprechende Extensionpoints im Plugin-Manifest einzutragen bzw. vorhandene Einträge zu bearbeiten. Dies ist auf zweierlei Arten möglich: Zum einen durch Verwendung des Plugin-Manifest Editors von Eclipse oder zum anderen durch Editieren der Datei plugin.xml mit einem Text-Editor.

Anmerkung zum Begriff Deklaration Der in diesem Artikel verwendete Begriff der Deklaration ist nicht zu verwechseln mit der Deklaration von Variabeln in Programmiersprachen. Es handelt sich dabei um eine "Bekanntmachung" im weiteren Sinn, nämlich daß eine Extension dem Eclipse-Framework bekanngemacht wird. Kern der Deklaration ist es jedoch eine bereitgestellte Extension mit einem im Eclipse-Framework definierten Extensionpoint zu verknüpfen.

Extensions und Extensionpoints

Das Eclipse-Framework kennt eine definierte Anzahl sogenannter Extensionpoints , die von Plugins durch sogenannte Extensions erweitert werden können. Ob und welche Extensionpoints ein konkretes Plug-In erweitert wird durch einen entsprechenden Eintrag in der Datei plugin.xml festgelegt. Im folgenden sind alle relevanten Extensionspoints für Builder,Marker,Natures,QuickFixes aufgelistet:


org.eclipse.core.resources.builders Builder-Deklaration
org.eclipse.core.resources.natures Nature-Deklaration
org.eclipse.core.resources.markers Marker-Deklaration
org.eclipse.ui.ide.markerResolution QuickFix-Deklaration


In diesem Artikel wird zum besseren Verständnis auf den Komfort des Plug-In-Manifest Editors verzichtet. Es sei an dieser Stelle jedoch darauf hingewiesen dass alle, hier gezeigten Einträge auch mittels Plug-In-Manifest Editor möglichen sind. Das Bearbeiten mittels Texteditor bietet keinerlei Vorteile, oder gar zusätzliche Einstellungsmöglichkeiten.

Der Begriff "Ressource" im Eclipse Framework

In den folgenden Abschnitten taucht immer wieder der Begriff "Ressource" auf, daher an dieser Stelle eine kurze Erläuterung was damit im Eclipse-Framework gemeint ist. Der Begriff Ressource bezeichnet Projekte,Verzeichnisse und Dateien, er ist somit eine Abstraktion für die Elemente die in einem Workspace enthalten sein können. Diese Elemente werden aus Sicht des Programmierers immer durch dass Interface IResource angesprochen. Das Eclipse Framework verwaltet alle Ressourcen ,hinsichtlich etwaiger Änderungen (Add,Remove,Change) und generiert entsprechende Events die dem Plugin zur Verfügung stehen, diese Events wiederum liefern dem Plugin Objekte von Typ IResource. Das Interface IResource stellt dann in weiterer Folge eine Methode getProject()zur Verfügung über das zugehörige Projekt ermittelt werden kann. Ressourcen können über den zugehörigen Workspace (verkörpert durch ein Objekt vom Typ IWorkspace) angelegt,gelöscht und verändert werden. Der Plug-In Programmierer hat somit nicht die Organisation und Veränderungen des Dateisystem im Fokus sondern das Objektmodell dass durch die Ressourcen-Hierarchie aufgebaut wird, diese lässt sich z.B. im Project-Explorer einsehen.

Programmier-Modelle

Builder

Natures

Marker

QuickFixes

Beispielimplementierung eines Builders für XML-Konfigurationsdateien mit Eclipse

Use-Case

Eine Java-Applikation soll zur Laufzeit mittels einer XML-Konfigurationsdatei konfiguriert werden können.Ziel ist es daß der Entwickler neue Konfigurationsparameter durch Einfügen einer neuen Methode in eine eigens definierte Java-Klasse anlegen kann - die XMl-Datei soll immer automatisch angepasst werden. Dies gilt auch bei Änderung von bestehenden Parametern und Löschen von Parametern. Zusätzlich soll der Entwickler beim Editieren der Konfigurationsklasse unterstützt werden, wenn es darum geht allfällige Konventionen einzuhalten.

Lösungsspezifikation

Die einzelnen Konfigurationsparameter sollen vom Entwickler in Form einer Get-Methode in der Java-Klasse ConfigParams festgelegt werden. Der Name der Methode soll dem Namen des Konfigurationsparameters zuzüglich des Prefixes get entsprechen. Weiters soll der Rückgabewert der Get-Methoden typisiert sein. Jeder Methode soll ein Default-Wert zugewiesen werden können, dies wird durch eine Kommentarzeile direkt vor der Methode ausgedrückt. Zusätzlich soll jede Methode explizit mit einem Code-Kommentar versehen werden, über den der Entwickler eine Beschreibung des Parameters angeben muss, der dann als XML-Kommentar in der Konfigurations-Datei erscheint. Die Klasse ConfigParams soll ausser den Get-Methoden und dem Konstruktor keine anderen public-Methoden besitzen. Die zulässigen Typen beschränken sich auf String,double,float,int,boolean.

Der zu implementierende Builder "XMLCfgBuilder" soll aus dem Quellcode der Klasse ConfigParams eine XML-Datei ConfigParams.xml erstellen, in der alle Konfigurationsparameter in Form von XML-Elementen abgebildet sind. Die Datei ConfigParams.xml ist in der IDE bei allfälligen Änderungen an der Quellcode-Datei ConfigParams.java entsprechend zu aktualisieren.

Mittels Marker soll dem Benutzer signalisert werden, falls in der Quellcode-Datei die oben definierten Konventionen nicht eingehalten werden. Dies sind

  • "PublicNoneGetMarker" Public-Methode ohne get-Prefix
  • "NoDefaultValueMarker" Get-Methode ohne DefaultValue
  • "NoDescriptionMarker" Get-Methode ohne Description
  • "InvalidTypeMarker" Get-Methode mit einem Typ ausserhalb der zulässigen Typen

Zu den Markern sollen entsprechende Quick-Fixes vorgesehen werden

  • Modifier auf private ändern - "PublicNoneGetMarker"
  • DefaultValue einfügen - "NoValueMarker"
  • Kommentar einfügen - "NoCommentMarker"
  • Typ anpassen "InvalidTypeMarker"

Über eine Nature "XMLCfgNature" soll dem Builder angezeigt werden welche Projekt von diesem Mechanismus Gebrauch machen und bei Änderungen zu builden sind. Die Nature soll weiters sicherstellen dass sie nur einem Java-Projekten zugewiesen werden kann.

Beispiel ConfigParams-Klasse

     public class ConfigParams {
 
	Hashtable<String,String> configValues;
	public ConfigParams(){
		//ConfigParams.xml einlesen oder anlegen
	}
 
	//The Username used for Dabatase-Login
        //DefaultValue="Admin" 
	public String getDBUserName(){
		return configValues.get("DBUserName");
	}	
 
	//The TCP-Port for the Server-Connection
        //DefaultValue=8080
	public int getServerPort(){
		return Integer.parseInt(configValues.get("ServerPort"));
	}
    }

Beispiel ConfigParams.xml

  <?xml version="1.0" encoding="UTF-8"?>
  <ConfigParams  
    <!-- "Get's the Username used for Dabatase-Login" --> 
    <DBUserName value="MyName" Type="String"></DBUserName>  
    <!-- "The TCP-Port for the Server-Connection" --> 
    <ServerPort value="8080" Type="Int"></ServerPort>  
  </ConfigParams>

Anmerkung zur Spezifikation

Die hier gezeigte Lösung hat natürlich noch einiges an Optimierungspotenzial was die Unterstützung des Entwicklers betrifft. Besonders das Einfügen der Methodenrümpfe die sich nur geringfügig unterscheiden könnte mit entsprechenden Maßnahmen wesentlich vereinfacht werden. Der Fokus dieses Artikels ist jedoch die Demonstration des Einsatzes der Plug-In-Komponenten Builder,Natures,Marker,QuickFixes zur Lösung der Aufgabenstellung. Um die Lösung möglicht nachvollziehbar zu gestalten ist die Einfachheit der Lösung gewollt und zweckdienlich. Es wird daher bewusst darauf verzichtet viel Aufwand in das Software-Design zu investieren oder alle erdenklichen Features einzubauen.

Umsetzung

Die oben spezifizierte Lösung soll nun mit Eclipse in ein Plug-In umgesetzt werden. Die folgende Beschreibung der Umsetzung ist in Form einer Anleitung zu sehen die Code,Deklaration,Debuggen und Bedienung zeigt. Die Anleitung zur Bedienung beschränkt sich allerdings auf die Dialoge zur Bearbeitung der Plugin-Manifests und die Einbindung des Plugins in ein Projekt. Der Umgang mit Eclipse , bzw. das Programmieren in Java ist nicht Inhalt dieser Anleitung. Um die Übersicht zu bewahren werden weiters Bedienungsfunktionen die ganze Serien von Eclipse-Dialogen zur Folge haben nur dann in Form von Screenshots eingefügt wenn es die Umstände erfordern,ansonsten wird textuell auf die Bedienfolgen verwiesen - die entsprechende Notation ist z.B. "plugin.xml"->"[KontextMenü]"->"Open with"->"Manifest Editor". Dabei beziehen sich sämtliche Texte die aus dem GUI der Eclipse-IDE stammen auf die englische Sprache.

Das Plugin-Projekt

Die Erstellung eiens Eclipse-Plug-Ins mit der Eclipse IDE basiert zunächst auf einem Eclipse Projekt das wie üblich in einem Workspace anzulegen ist. Ein Plugin-Projekt entspricht, bezüglich Struktur und den darin enthaltenen Artefakten, im wesentlichen einem "normalen" Java Projekt. Die Unterschiede bestehen hauptsächlich in den zusätzlichen Dependencies zu den Packages des Eclipse-Frameworks und den Plugin-Manifest Dateien, sowie die spezifische .project Datei.

Erstellen eines Plugin-Projekt

Builder Teil 1

Als Nächstes ist die Deklaration des Builders vorzunehmen. Dazu sind die im vorangehenden Abschnitt genannten Einträge in die Datei plugin.xml zu machen. Das Projektgerüst enhält allerdings zu diesem Zeitpunkt noch keine plugin.xml. Wie ebenfalls im vorangehenden Abschnitt erwähnt wurde besteht die Möglichkeit die Datei plugin.xml via Text-Editor oder mittels Manifest-Editor zu bearbeiten, im folgenden werden wir den Manifest-Editor verwenden. Der Manifest-Editor hat zusätzlich den Vorteil daß er die Datei plugin.xml automatisch erstellt, falls sie noch nicht vorhanden ist.

Manifest Editor
Builder-Extensionpoint auswählen
Extension Details

Als Nächstes ist im Package fuh.xmlconfig.plugin eine neue Klasse "XMLCfgBuilder" anzulegen

Die Klasse XMLCfgBuilder schaut aus wie folgt:

  package fuh.xmlconfig.plugin;
 
  import java.util.Map;
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.resources.IMarker;
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.IResourceDelta;
  import org.eclipse.core.resources.IResourceDeltaVisitor;
  import org.eclipse.core.resources.IResourceVisitor;
  import org.eclipse.core.resources.IncrementalProjectBuilder;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IProgressMonitor;
 
  public class XMLCfgBuilder extends IncrementalProjectBuilder {
 
	@Override
	protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
	throws CoreException {
		//TODO Hier die Build-Logik implementieren
		return null;
	}
  }

Das Plug-In ist dieser Form ,zumindest aus Sicht des Eclipse Frameworks, schon voll funktionstüchtig. Damit nun der Aufruf des Builders durch das Eclipse-Framework möglich ist müssen wir an dieser Stelle zur Umsetzung der Nature wechseln, dies im Wissen das die eigentliche Funktionalität des Builders noch fehlt. Aber um eben diese in weitere Folge zu testen muss das Plugin nun mit dem Testprojekt verknüpft werden.

Nature

Um das Testpojekt fuh.xmlconfig.testproject mit dem Builder aus dem Plug-In fuh.xmlconfig.plugin zu verknüpfen wird eine neue Nature deklariert.

Verknüpfung mit dem Extensionpoint
Zuweisung der Klasse für die Nature


Anmerkung: Die somit definierte Nature wird dann in weiterer Folge (siehe unten) einem entsprechenden Testprojekt zugewiesen.

Testen und Debuggen

Das Plug-In kann nun über den Debugger gestartet werden. Dabei startet Eclipse eine zweite Instanz der Eclipse IDE und verbindet den Debugger darauf. In dieser zweiten Instanz kann man nun ein Java-Projekt anlegen. Die zweite Eclipse-Instanz bekommt automatisch einen eigenen Workspace zugeordnet, der standardmäßig im Verzeichnis "runtime-EclipseApplication" angelegt wird. Das Plug-In wird in der neuen Instanz automatisch eingebunden, falls es als Plug-In exportiert wurde. Wird nun im erstellten Java-Projekt eine neue Java-Klasse angelegt und gespeichert, so wird die Methode build des XMLCofgBuilders aufgerufen. Wenn dies zutrifft ist die Konfiguration und Implementierung korrekt. Falls die build-Methode nicht aufgerufen wird, liegt meist ein Konfigurationsfehler vor. Wenn der Fehler trotz intensiver Suche nicht gefunden wird empfiehlt es sich eine "manuelle" Umschaltung der Nature vorzunehmen und dabei von einem Haltepunkt im Code der Nature-Klasse weiter in das Eclipse- Framework zu steppen. Dort lässt sich gut nachvollziehen welche Konfigurationsdaten Eclipse auswertet und warum z.B. ein Builder nicht instanziert wird. Als heisser Tipp sei an dieser Stelle die Klasse BuildManager und die Methode instantiateBuilder genannt. Dort kann gut nachvollzogen werden warum z.b. ein Builder nicht zur Anwendung kommt.

       private IncrementalProjectBuilder instantiateBuilder(String builderName) throws CoreException {
		IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, builderName);
		if (extension == null)
			return null;
		IConfigurationElement[] configs = extension.getConfigurationElements();
		if (configs.length == 0)
			return null;
		String natureId = null;
		if (getBooleanAttribute(configs[0], "hasNature")) { //$NON-NLS-1$
			//find the nature that owns this builder
			String builderId = extension.getUniqueIdentifier();
			natureId = workspace.getNatureManager().findNatureForBuilder(builderId);
			if (natureId == null)
				return null;
		}
		//The nature exists, or this builder doesn't specify a nature
		InternalBuilder builder = (InternalBuilder) configs[0].createExecutableExtension("run"); //$NON-NLS-1$
		builder.setPluginId(extension.getContributor().getName());
		builder.setLabel(extension.getLabel());
		builder.setNatureId(natureId);
		builder.setCallOnEmptyDelta(getBooleanAttribute(configs[0], "callOnEmptyDelta")); //$NON-NLS-1$
		return (IncrementalProjectBuilder) builder;
	}

Das Test-Projekt

Bei der Entwicklung eines Plug-Ins ist es praktisch wenn man schon von Beginn weg ein Test-Projekt zur Verfügung hat auf dem das Plug-In laufend getestet werden kann. In unserem Fall genügt ein einfaches Java-Projekt mit einer Main-Klasse der XMLConfigParams-Klasse. Der Zugriff auf die Konfigurationsparameter kann exemplarisch in der main-Methode erfolgen.

-Schritt 1: Neues Java-Projekt anlegen

-Schritt 2: Neue Klasse XMLConfigParams hinzufügen

-Schritt 3: Neue Annotation XMLConfigParamDescription hinzufügen

-Schritt 4: Neue Klasse XMLHelper hinzufügen


Das neue Projekt hat folgenden Inhalt:

TestProject1.png


-Schritt 5: Zuweisung der Nature an das Testprojekt

Um dem Testprojekt die eben deklarierte Nature zuzuweisen öffnen wir die .project-Datei mittels Texteditor und fügen die notwendigen Einträge, wie bereits in vorhergehenden Abschnitt beschrieben hinzu.

Die .project Datei von fuh.xmlconfig.testproject davor

    <?xml version="1.0" encoding="UTF-8"?>
    <projectDescription>
	<name>fuh.xmlconfig.testproject</name>
	<comment></comment>
	<projects>
	</projects>
	<buildSpec>
	   <buildCommand>
		<name>org.eclipse.jdt.core.javabuilder</name>
		<arguments>
		</arguments>
	   </buildCommand>
	</buildSpec>
	<natures>
		<nature>org.eclipse.jdt.core.javanature</nature>
	</natures>
     </projectDescription>


Die .project Datei von fuh.xmlconfig.testproject danach

    <?xml version="1.0" encoding="UTF-8"?>
    <projectDescription>
	<name>fuh.xmlconfig.testproject</name>
	<comment></comment>
	<projects>
	</projects>
	<buildSpec>
	     <buildCommand>
		<name>org.eclipse.jdt.core.javabuilder</name>
		<arguments>
		</arguments>
                <name>fuh.xmlconfig.plugin.XMLCfgBuilder</name>  
	     </buildCommand>
	</buildSpec>
	<natures>
	     <nature>org.eclipse.jdt.core.javanature</nature>
             <nature>fuh.xmlconfig.plugin.XMLCfgProjectNature</nature> 
	</natures>
     </projectDescription>

Builder Teil 2

Wir setzen an dieser Stelle mit der Implementierung des Builders fort. Das Grundgerüst des Plug-Ins ist nun voll funktionsfähig, der Builder wird vom Eclipse-Framework aufgerufen sobald sich Ressourcen ändern. Nun folgt die spezifische Funktion die der Builder zu erfüllen hat, eben die Erstellung einer XML-Datei aus einer Java-Klasse.

Entwurf: Bei Aufruf des Builders wird in einem ersten Schritt geprüft werden welche Dateien sich geändert haben, und ob eine der betroffenen Dateien eine XMLConfig Java-Datei ist. Falls das zutrifft wird im nächsten Schritt die betreffende Datei von einem einfachen Parser analysiert,in die relevanten Bestandteile zerlegt und daraus eine Liste erstellt. Im letzten Schritt wird dann aus der Elementliste des Parsers eine XML-Datei generiert.


public class XMLCfgBuilder extends IncrementalProjectBuilder {
 
	public static final String BUILDER_ID = "fuh.xmlconfig.plugin.xmlcfgbuilder";
	public static final String XML_FILENAME = "ConfigParams.xml";
 
	@Override
	protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
	throws CoreException {
 
		System.out.println("XMLCfgBuilder Building");
 
		//Prüfen welche Dateien sich geändert haben
		IFile codeFile = filterFiles();
		//Falls sich die ConfigParams_Datei geändert hat builden
		if(codeFile != null){
 
			List<CfgProperty> propList = parseCodeFile(codeFile);
 
			String xmlFilePath = getProject().getLocation().toString() + "/" + XML_FILENAME;
			generateXMLFile(propList,xmlFilePath);
		}
 
		return null;
	}
 
	//Analysiert die Liste der geänderten Ressourcen und gibt die ConfigParams-Datei zurück falls
	//diese unter den Geänderten ist.
	public IFile filterFiles(){
 
		IResourceDelta resDelta = this.getDelta(getProject());
		if(resDelta == null)
			return null;
 
		DeltaVisitor visitor = new DeltaVisitor();
 
		try {
 
			resDelta.accept(visitor);
		} catch (CoreException e) {			
			e.printStackTrace();
		}
 
		if(visitor.getConfigParamsChanged())				
			return visitor.getConfigParamsFile();
		else
			return null;
	}
 
	//Analysiert das Java-File und gibt eine Liste der gefunden Getter zurück
	public List<CfgProperty> parseCodeFile(IFile CodeFile){
 
 
		List<CfgProperty> result = new ArrayList<CfgProperty>();
 
		List<String> codeLines=readLines(CodeFile);		
 
		for(int i=0;i<codeLines.size();i++)
		{
                   ... //Analyse-Logik		
		}			
 
		return result;
	}
 
	//Erzeugt aus den Getter-Infos das XML-File
	public void generateXMLFile(List<CfgProperty> PropertyList,String XMLFilePath){
 
		... //XML-Document erstellen	
 
	}
 
	//Schreibt das XML-Dokument in eine Datei
	private void writeToFile(Document XMLDoc,String XMLFilePath){
 
		 ...//XML-Document in die Datei schreiben
	}
 
	//Liest das Java-Codefile zeilenweise ein
	private List<String> readLines(IFile CodeFile){
 
	    List<String> result = new ArrayList<String>();
 
	    try 
	    { 	
	    	InputStream is;			
			is = CodeFile.getContents();
 
	    	...//Datei zeilenweisen einlesen
 
	    } catch (FileNotFoundException e) {
	      e.printStackTrace();
	    } catch (IOException e) {
	      e.printStackTrace();
	    }catch (CoreException e) {
		      e.printStackTrace();
	    }
 
	    return result;
	}
 
 
}


In der Methode filterFiles wird festgestellt ob sich die für den Builder relevante Datei ConfigParams.java geändert hat. Dazu wird der Visitor DeltaVisitor implementiert. Die Implementierung schaut aus wie folgt:

package fuh.xmlconfig.plugin;
 
import java.io.*;
 
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
 
public class DeltaVisitor implements IResourceDeltaVisitor {
 
	private boolean ConfigParamsChanged;
	private IFile ConfigParamsFile;
	public static String CONFIG_PARAM_FILENAME = "ConfigParams.java";
 
    public boolean visit(IResourceDelta delta) {
        switch (delta.getKind()) {
        case IResourceDelta.ADDED :
            // handle added resource
            break;
        case IResourceDelta.REMOVED :
            // handle removed resource
            break;
        case IResourceDelta.CHANGED :
            // handle changed resource
            break;
        }
 
        String resName = delta.getResource().getName();
        ConfigParamsChanged =  resName.equalsIgnoreCase(CONFIG_PARAM_FILENAME);
        if(ConfigParamsChanged)
        	ConfigParamsFile = delta.getResource().getProject().getFile(delta.getResource().getProjectRelativePath());
 
        return true;
    }
 
    public boolean getConfigParamsChanged()
    {
    	return ConfigParamsChanged;
    }
 
    public IFile getConfigParamsFile()
    {
    	return ConfigParamsFile;
    }
}


Details Damit ist die Grund-Funktionalität des Builders bereits erfüllt. Der Builder wird bei Änderungen an Dateien im zugehörigen Projekt aufgerufen, das Changeset wird analysiert, falls sich die für den Builder interessante Datei "ConfigParams.java" unter den geänderten Ressourcen befindet wird daraus die korrespondierende XML-Datei erzeugt und im Projekt-Root Verzeichnis abgelegt. Dabei wird kein Unterschied gemacht welche Art Änderung erfolgt ist, was in diesem Fall auch keinen Unterschied machen würde. Falls es gewünscht wäre das Verhalten je nach Art der Änderung "ADDED,REMOVED,CHANGED" zu ändern, wäre dies in der Methode DeltaVisitor.visit zu implementieren, ein entsprechendes Gerüst für die Fallunterscheidung ist dort bereits vorhanden. Die Namen der Quell-und Ausgabedateien können ggf. in den entsprechenden Konstanten CONFIG_PARAM_FILENAME und XML_FILENAME angepasst werden.


Anmerkung zur Implementierung: Die syntaktische Analyse wie sie der Builder durchführt ist bewusst einfach gewählt, da der Fokus auf der Demonstration der Anwendung des Builders auf eine Ressource liegt. Natürlich würde man hier auf einen "richtigen" Parser für Java Quellcode zurückgreifen und die XML-Datei auf dem erzeugten AST generieren. Da hier davon ausgegangen wird daß der Java-Quellcode zumindest syntaktisch korrekt ist, beschränken wir uns auf die Prüfung der spezifizierten Programmier-Konventionen.

Marker

Die Lösung weist nun noch einige Schwächen auf, denn sie funktioniert nur solange der Entwickler sich bei Änderungen an der Datei ConfigParams.java strikt an die vereinbarten Konventionen hält. Beispielsweise geht der Builder davon aus daß jeder Getter eine Kommentarzeile für die Beschreibung und eine Kommentarzeile für den DefaultValue enthält. Weiters wird davon ausgegangen daß alle Public-Methoden mit dem Prefix "get" beginnen einen Konfigurationsparameter darstellen , also müßten diese auch einen Rückgabewert innerhalb der zulässigen Typen haben. Der Java-Compiler wird in diesen Fällen keinen Fehler anzeigen da der Code der Klasse syntaktisch korrekt sein würde. Um diese Konventionen zu überprüfen und dem Entwickler allfällige Verletzungen anzuzeigen bieten sich Marker geradezu an. Im Folgenden sollen nun für die in der Spezifikation festgelegten Regelverletzungen entsprechende Marker eingeführt und implementiert werden.

Dazu nochmals die zu prüfenden Regeln:

  1. "PublicNoneGetMarker" Signalisiert daß die ConfigParams-Klasse eine public-Methode enthält deren Name kein get-Prefix hat
  2. "NoDefaultValueMarker" Signalisiert daß die ConfigParams-Klasse eine Get-Methode enthält die keinen DefaultValue-Kommentar besitzt
  3. "NoDescriptionMarker" Signalisiert daß die ConfigParams-Klasse eine Get-Methode enthält die keinen Description-Kommentar besitzt
  4. "InvalidTypeMarker" Signalisiert daß die ConfigParams-Klasse eine Get-Methode deren Rückgabewert einen Typ ausserhalb der zulässigen Typen darstellt

An dieser Stellen detaillieren wir die Spezifikation zusätzlich dahingehend daß alle Marker als Problem-Marker visualisiert und nicht persistent gespeichert werden sollen.

Anmerkung Es wären natürlich noch weitere Prüfungen denkbar, um die Implementierung und Funktionsweise zu demonstrieren sind die hier spezifizierten allerdings mehr als ausreichend.


Wie bereits in Teil 1 erwähnt wird für die Definition eines Markers nur ein entsprechender Eintrag im Plug-In-Manifest plugin.xml benötigt, dieser muss sich auf den Extensionpoint "org.eclipse.core.resources.markers" beziehen.

Extension für Marker hinzufügen
Details zum Marker

Diese Schritte werden nun für alle 4 Marker wiederholt, wobei bei jedem Durchlauf der jeweilige Marker-Typ als Id und eine dazu passende Beschreibung als Name eingesetzt wird.

Alle Marker


Die Anwendung der somit definierten Markertypen findet nun an genau der Stelle statt an der die Konventionen geprüft werden, nämlich bei der Analyse der Java-Codedatei in der Methode XMLCfgBuilder.parseCodeFile. Eine wichtige Information die alle Marker bereits als Standard-Attribut vorsehen ist dabei die Zeilennummer an der das Problem gefunden wurde. Da unser Parser die Quellcode-Datei bereits zeilenweise abarbeitet ist diese Information bereits durch den Schleifenzähler gegeben, da dieser aber 0-basierend ist muss für die effektive Zeilenummer 1 dazugezählt werden.

Wir ergänzen worerst die Builder-Klasse XMLCfgBuilder um 4 Konstanten für die ID's der 4 definierten Marker und weiters um das Erstellen der Marker etwas zu vereinfachen um eine Hilfsmethode die dann an allen Stellen an denen Verletzungen der Konventionen festgestellt werden aufgerufen wird.

public class XMLCfgBuilder extends IncrementalProjectBuilder {
 
        ...
 
        private static final String PUBLIC_NONE_GET_MARKER = "fuh.xmlconfig.plugin.PublicNoneGetMarker";        private static final String NO_DEFAULTVALUE_MARKER = "fuh.xmlconfig.plugin.NoDefaultValueMarker";        private static final String NO_DESCRIPTION_MARKER = "fuh.xmlconfig.plugin.NoDescriptionMarker";        private static final String INVALID_TYPE_MARKER = "fuh.xmlconfig.plugin.InvalidTypeMarker"; 	private void addMarker(String MarkerType,IFile file, String message, int lineNumber,int severity) { 	       try {	             IMarker marker = file.createMarker(MarkerType);	             marker.setAttribute(IMarker.MESSAGE, message);	             marker.setAttribute(IMarker.SEVERITY, severity);	             if (lineNumber == -1) {	                lineNumber = 1;	             }	             marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);	        }	        catch (CoreException e) {}	  } 
          ...
 
 
}


Um die erste Regel unserer Konventionen zu prüfen ergänzen wir nun die Methode parseCodeFile wie folgt:

	public List<CfgProperty> parseCodeFile(IFile CodeFile){
 
		List<CfgProperty> result = new ArrayList<CfgProperty>();
 
		List<String> codeLines=readLines(CodeFile);
 
		String curComment = "";
		String curDefaultValue = "";
 
		for(int i=0;i<codeLines.size();i++)
		{
			String codeLine = codeLines.get(i).trim();	
			String[] lineBuff = codeLine.split(" ");	
 
		    //Public-Methoden suchen, der Konstruktor ist nicht relevant
		    if(codeLine.startsWith("public"))
		    {   		    	
                        if(lineBuff.length >= 3 && lineBuff[2].startsWith("get"))
                        { 
		    	    if(curComment == null)		    	    	addMarker(NO_DESCRIPTION_MARKER,CodeFile,"Der Getter hat keinen Kommentar mit Beschreibung",i+1,IMarker.SEVERITY_ERROR);	    		 
 
		    	    ...//weitere Analyse-Logik
 
		}			
 
		return result;
	}


Wenn wir nun das Testprojekt starten und in der Klasse ConfigParams einen Getter implementieren bei dem eben der Description-Kommentar fehlt, wird der Problem-Marker wie oben definiert angezeigt. Dabei wird direkt im Editor das Standard-Problem-Icon an genau der betreffenden Zeilennummer angezeigt. Zusätzlich erscheint der Marker in der Problem-View mit den enstprechenden Detail-Informationen. Dabei sorgt die Problem View automatisch dafür daß der Wert der im Attribut "Message" angegeben wurde in der Spalte "Description" erscheint, weiters der Wert im Attribut "Location" in der Spalte "Location".

Visualisierung des Markers


Anmerkung: Die hier gezeigte Implementierung des Builders setzt die Erstellung der Ausgabedatei fort auch wenn die geprüfte Konvention verletzt wird. In der erstellten XML-Datei würde in diesem Fall z.B. der Kommentar für die Beschreibung fehlen. Dies ist eine persönliche Entscheidung des Autors und muss im konkreten Fall natürlich überdacht werden.


Nun können wir die restlichen 3 Marker implementieren dabei gehen wir nach demselben Schema vor, einzig die Erkennung der entsprechenden Verletzung unterscheidet sich. Die Methode parseCodeFile im folgenden mit der vervollständigten Fehlerprüfung:

public List<CfgProperty> parseCodeFile(IFile CodeFile){
 
		List<CfgProperty> result = new ArrayList<CfgProperty>();
 
		List<String> codeLines=readLines(CodeFile);
 
		String curComment = "";
		String curDefaultValue = "";
 
		for(int i=0;i<codeLines.size();i++)
		{
		    String codeLine = codeLines.get(i).trim();	
		    String[] lineBuff = codeLine.split(" ");	
 
			//Public-Methoden suchen, der Konstruktor ist nicht relevant
		    if(codeLine.startsWith("public") )
		    {   		    	
		    	if(lineBuff.length >= 3 && lineBuff[2].startsWith("get"))
		    	{
		    		if(curComment == null)		    			addMarker(NO_DESCRIPTION_MARKER,CodeFile,"Der Getter hat keinen Kommentar mit Beschreibung",i+1,IMarker.SEVERITY_ERROR);	    		 		    		if(curDefaultValue==null)		    			addMarker(NO_DEFAULTVALUE_MARKER,CodeFile,"Der Getter hat keinen Kommentar mit einem DefaultValue",i+1,IMarker.SEVERITY_ERROR);	 
 
		    		int startBrack = lineBuff[2].indexOf("(");
		    		String name = lineBuff[2].substring(3, startBrack);		    
		    		String dataType = lineBuff[1];	
 
		    		if(isValidType(dataType))
		    		{
		    			CfgProperty cfgProp = new CfgProperty(name,curDefaultValue,dataType,curComment);
		    			result.add(cfgProp);
		    		}
		    		else		    			addMarker(INVALID_TYPE_MARKER,CodeFile,"Der Getter hat einen ungültigen Rückgabewert ",i+1,IMarker.SEVERITY_ERROR);	   		    					    	}		    	else		    		addMarker(PUBLIC_NONE_GET_MARKER,CodeFile,"Die Methode ist public aber kein Getter",i+1,IMarker.SEVERITY_ERROR);	    
		    	...//weitere Analyse-Logik
		    }
 
		    ...//weitere Analyse-Logik
		}


Alle definierten Marker werden z.B. folgendermassen ausgegeben:

Alle Marker


Die Implementierung der Marker funktioniert soweit, die geprüften Verletzungen der Konventionen werden korrekt dargestellt. Allerdings werden mit dieser Lösung bei wiederholten Builds bereits angezeigte Marker immer wieder neu hinzugefügt und sind somit mehrfach vorhanden. Um dies zu verhindern stellt das Eclipse-Framework in der Klasse Project die Methode deleteMarkers bereit. Über entsprechende Parameter erlaubt die Methode das selektive Löschen der Marker vor jedem Build. Wir definieren dazu eine Hilfsmethode die wir direkt in der XMLCfgBuilder.build-Methode aufrufen:

public class XMLCfgBuilder extends IncrementalProjectBuilder {
 
        ...
 
        private void deleteMarkers(){ 		try {			getProject().deleteMarkers(PUBLIC_NONE_GET_MARKER , false, IResource.DEPTH_INFINITE);			getProject().deleteMarkers(NO_DEFAULTVALUE_MARKER , false, IResource.DEPTH_INFINITE);			getProject().deleteMarkers(NO_DESCRIPTION_MARKER , false, IResource.DEPTH_INFINITE);			getProject().deleteMarkers(INVALID_TYPE_MARKER , false, IResource.DEPTH_INFINITE);		} catch (CoreException e) {						e.printStackTrace();		}	} 
 
        @Override
	protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
	throws CoreException {
 
		System.out.println("XMLCfgBuilder Building");
 
		//Prüfen welche Dateien sich geändert haben
		IFile codeFile = filterFiles();
		//Falls sich die ConfigParams_Datei geändert hat builden
		if(codeFile != null){
 
			deleteMarkers();			List<CfgProperty> propList = parseCodeFile(codeFile);
 
			String xmlFilePath = getProject().getLocation().toString() + "/" + XML_FILENAME;
			generateXMLFile(propList,xmlFilePath);
		}
 
		return null;
	}

Die Visualisierung der Marker lässt sich noch weiter verbessern wenn neben der betreffenden Zeilennummer im Editor auch die Codestelle markiert wird. Dazu stehen die vom Eclipse-Framework vordefinierten Attribute CHAR_START und CHAR_END zur Verfügung. Wenn dem Marker diese beiden Attribute hinzugefügt werden, wird die betreffenden Codestelle zusätzlich hervorgehoben. Im Falle unserers Beispiels macht dies jedoch nicht bei allen Markern Sinn, da manche von ihnen sich auf eine ganze Zeile beziehen. Wir nehmen daher als Beispiel den Marker INVALID_TYPE_MARKER. Sollte also ein Getter einen, gemäß der Spezifikation ungültigen Rückgabewert besitzen, so soll genau das für den Rückgabewert deklarierte Typ-Token hervorgehoben werden.

Um die betreffenden Zeilennummern zu ermitteln wird die Methode <cod>XMLCfgBuilder.parseCodeFile</code> angepasst, weiters wird die Signatur der Methode addMarker um die beiden Parameter ChartStart und CharEnde erweitert. Die Änderungen sind in folgendem Code-Ausschnitten hervorghoben:

public List<CfgProperty> parseCodeFile(IFile CodeFile){
 
		List<CfgProperty> result = new ArrayList<CfgProperty>();
 
		List<String> codeLines=readLines(CodeFile);
 
		String curComment = "";
		String curDefaultValue = "";
		int fullLineCharCount = 0;		for(int i=0;i<codeLines.size();i++)
		{	
 
			String codeLine = codeLines.get(i).trim();	
			String[] lineBuff = codeLine.split(" ");	
 
			//Public-Methoden suchen, der Konstruktor ist nicht relevant
		    if(codeLine.startsWith("public") && codeLine.indexOf("ConfigParams") < 0 )
		    {   		    	
		    	if(lineBuff.length >= 3 && lineBuff[2].startsWith("get"))
		    	{
		    		if(curComment == null)
		    			addMarker(NO_DESCRIPTION_MARKER,CodeFile,"Der Getter hat keinen Kommentar mit Beschreibung",i+1,0,0,IMarker.SEVERITY_ERROR);	    		
 
		    		if(curDefaultValue==null)
		    			addMarker(NO_DEFAULTVALUE_MARKER,CodeFile,"Der Getter hat keinen Kommentar mit einem DefaultValue",i+1,0,0,IMarker.SEVERITY_ERROR);	
 
 
		    		int startBrack = lineBuff[2].indexOf("(");
		    		String name = lineBuff[2].substring(3, startBrack);		    
		    		String dataType = lineBuff[1];	
 
		    		if(isValidType(dataType))
		    		{
		    			CfgProperty cfgProp = new CfgProperty(name,curDefaultValue,dataType,curComment);
		    			result.add(cfgProp);
		    		}
		    		else
		    		{
		    			int chrStart =  fullLineCharCount + codeLines.get(i).indexOf("public") + 1 +  lineBuff[0].length();		    			int chrEnd = chrStart + lineBuff[1].length();		    			addMarker(INVALID_TYPE_MARKER,CodeFile,"Der Getter hat einen ungültigen Rückgabewert ",i+1,chrStart,chrEnd,IMarker.SEVERITY_ERROR);	   			    		}		    	
 
                    ...//Weitere Analyse-Logik 
 
		}			
 
		return result;
	}
 
private void addMarker(String MarkerType,IFile file, String message, int lineNumber,int CharStart,int CharEnd,int severity) {
 
	       try {
	             IMarker marker = file.createMarker(MarkerType);
	             marker.setAttribute(IMarker.MESSAGE, message);
	             marker.setAttribute(IMarker.SEVERITY, severity);
	             if(CharEnd > 0)	             {	            	 marker.setAttribute(IMarker.CHAR_START, CharStart);	            	 marker.setAttribute(IMarker.CHAR_END, CharEnd);	             }	             if (lineNumber == -1) {
	                lineNumber = 1;
	             }
	             marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
	        }
	        catch (CoreException e) {}
	  }


Nun zeigt der Editor beim betreffenden Marker zusätzlich zur Zeilenummer die genaue Zeichenposition an, symbolisiert durch die charakteristische rote wellenförmige Unterschreichung, wie im Bild unten zu sehen.

Marker mit Visualisierung an Zeichenposition

Quickfix

Das entwickelte Plug-In gibt dem Anwender nun schon recht detailliert Auskunft über die genaue Stelle an der ein Fehler gefunden wurde und dessen Ursache. Mit diesen Informationen kann beispielsweise durch Klick auf die betreffenden Zeile in der Problem View leicht direkt an die betreffenden Stelle gesprungen werden um den Fehler zu korrigieren. Aber auch hier bietet das Eclipse Framework zuätzliche Unterstützung eben in Form der sogenannten "Quick Fixes" oder auch "Marker Resolutions". Je genauer nun einzelne Typen von Markern einen gefunden Fehler (formal) beschreiben umso mehr lässt sich die Korrektur des Problems nun automatisieren. Mit Automatisieren ist jedoch nur der Korrekturvorgang selber gemeint, es bedarf letztlich immer eines Benutzereingriffs und sei es nur um eine von mehreren Korrektur-Alternativen auzuwählen. Genau das sollen wir nun in unser Plug-In einbauen.

Wir erinnern uns an unsere 4 Marker-Typen, zu diesen definieren wir nur jeweils entsprechenden Korrektur-Alternativen die wir dann in Form von Quick-Fixes implementieren werden:

  1. "PublicNoneGetMarker" Quick-Fix: Access-Modifier auf private ändern
  2. "NoDefaultValueMarker" Quick-Fix: DefaultValue-Kommentar einfügen mit Default-Value 0 für numerische Typen und "" für Strings
  3. "NoDescriptionMarker" Quick-Fix: Description-Kommentar einfügen mit Standard-Text
  4. "InvalidTypeMarker" Quick-Fix: Rückgabe-Typ wahlweise auf String,int,long,double,boolean abändern


Wie in der Einführung bereits gezeigt wurde, wird eine Marker durch eine Deklaration im Plug-In-Manifest und eine Implementierung der entsprechenden Klassen erstellt. Als erstes wollen wir die Deklaration vornehmen, dazu öffnen wir den Manifest-Edtior und fügen eine Extension für den Extensionpoint "org.eclipse.ui.ide.markerResolution" ein.

Extension für QuickFix hinzufügen
QuickFix -Extension Details


Diese Schritte sind nun für alle 4 Marker zu wiederholen, wobei jeweils, der im Feld markerType eingegebene Name des Markers anzupassen ist, der Eintrag im Feld class ist in unserem Beispiel für alle Marker derselbe, da wir nur eine einzige Generator-Klasse verwenden.


Somit ist die Deklaration der QuickFixes abgeschlossen als nächstes kann nun die Implementierung vorgenommen werden. Bei der Implementierung ist nun eine Generatorklasse und jeweils pro Marker eine Resolution-Klassen zu implementieren. Dabei bietet der Marker InvalidTypeMarker 5 QuickFixes, alle anderen nur 1. Wir werden allerdings beim InvalidTypeMarker alle 5 QuickFixes mit einer einzigen Klasse erledigen, die unterschiedlichen Varianten können durch die Instanzierung mit verschiedenen Typ-Werten erzeugt werden.

Die Generatorklasse muss das Interface IMarkerResolutionGenerator2 implementieren und für die Erzeugung der MarkerResolutions sorgen. Wir wollen hier als erstes die Implementierung für die Erzeugung des Markers PublicNoneGetMarker machen.


Implementierung der Generatorklasse MarkerResolutionGenerator

public class MarkerResolutionGenerator implements IMarkerResolutionGenerator2
{
 
	@Override
	public boolean hasResolutions(IMarker marker) {
 
		try{
			if(marker.getType().equals(XMLCfgBuilder.PUBLIC_NONE_GET_MARKER))
				return true;
			else
				return false;
		}
		catch(Exception ex)
		{
			return false;
		}		
	}
 
	@Override
	public IMarkerResolution[] getResolutions(IMarker marker) {
 
		List<IMarkerResolution> resolutions = new ArrayList<IMarkerResolution>();		
 
		resolutions.add(new PublicNoneGetMarkerResolution());		
 
		return (IMarkerResolution[])resolutions.toArray(new IMarkerResolution[resolutions.size()]);
	}
 
}


Implementierung der Klasse PublicNoneGetMarkerResolution

public class PublicNoneGetMarkerResolution implements IMarkerResolution2
{
 
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return "Access Modifier auf private setzen";
	}
 
	@Override
	public Image getImage() {
 
		return null;
	}
 
	@Override
	public String getLabel() {
 
		return "public -> private";
	}
 
	@Override
	public void run(IMarker marker) {
 
		IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
 
 
		try
		{
			//Referenz auf den aktuallen Editor holen
			IWorkingCopyManager manager = JavaUI.getWorkingCopyManager();						
			IEditorPart editPart = IDE.openEditor(page,marker); //3.0 API
			IEditorInput editInput= editPart.getEditorInput();
			manager.connect(editInput);
			//Inhalt des Editors adressieren
			ICompilationUnit cu = manager.getWorkingCopy(editInput);
			IBuffer buffer = cu.getBuffer();	
			String fileContent = buffer.getContents();
 
			//Inhalt zeileorientiert aufbereiten
			String[] lineBuffer = fileContent.split(System.getProperty("line.separator"));		
			int effektivLineNr = marker.getAttribute(marker.LINE_NUMBER, 0) -1;
			//Zeile mit Methodensignatur ermitteln		
			String curMethodSig = lineBuffer[effektivLineNr];
			//Methodensignatur auf private abändern
			String newMethodSig = curMethodSig.replaceFirst("public", "private");
			//Änderung der Methodensignatur im Editor-Buffer vornehmen
			fileContent = fileContent.replaceFirst("\\Q" + curMethodSig + "\\E", newMethodSig );
 
			//Datei Speichern, Editor aktualisieren
			buffer.setContents(fileContent);			
			cu.reconcile();
			cu.commitWorkingCopy(true, null);
 
			//Marker löschen			
			marker.delete();		
		}
		catch(PartInitException ex)
		{	
			ex.printStackTrace();
			return;
		} catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}	
}


Erläuterung: Die Klasse MarkerResolutionGenerator ist bereits im Abschnitt 1 hinlänglich beschrieben worden. Im Wesentlichen ermöglicht sie den Eclipse Framework festzustellen ob es zu einem Marker überhaupt einen Quickfix gibt, falls ja erzeugt sie die Instanzen der möglichen QuickFixes. Die Klasse PublicNoneGetMarkerResolution ist nun ein Quickfix für den Marker PublicNoneGetMarker. Diese Klasse sorgt für den entsprechenden Eintrag in der Liste der Quickfixes die vom Eclipse Framework angezeigt werden. Die eigentliche Funktionalität des QuickFixes ist dann in der Methode run() zu finden. Hier wird nun, basierend auf den Informationen des übergebenen Markers auf den aktuellen Editor zugegriffen und auf dessen Inhalt eine Ersetzung des Schlüsselwortes "public" durch "private" an der betreffenden Zeilennummer vorgenommen. Anschließend wird der Editor aktualisiert und die Datei gespeichert. Nach erfolgreicher Ausführung des QuickFixes wird der Marker gelöscht da das Problem nach Ausführung eines QuickFixes als behoben gilt.

Anmerkung: Die hier gezeigte Methode des Zugriffs und der Veränderung des Inhaltes eines Editors mit reiner String-Ersetzungs-Operationen ist in einfachen Fällen angebracht. Bei komplexer Analyse und Manipulation von Quellcode-Dateien bietet es sich natürlich an, die AST-basierenden Funktionen von Eclipse zu verwenden.


Der QuickFix für den Marker PublicNoneGetMarker funktioniert somit. Im Editor links wird nun, bei Auftreten einer Methode die "public" ist aber nicht mit dem Prefix "get" beginnt, signalisiert dass ein QuickFix zu Auswahl steht (symbolisiert durch eine Glühbirne kombiniert mit dem Error-Icon). In der Liste der QuickFixes steht nun ein Eintrag der mit Doppelklick ausgeführt werden kann. Nach Ausführen des QuickFixes ist die betreffende Methodensignatur im Editor korrigiert und trägt nun den Access-Modifier "private".

Visualisierung des QuickFixes



Wir können nun mit der Implementierung der restlichen QuickFixes fortsetzen, die Vorgangsweise entspricht, bis auf die spezifische Manipulation der betreffenden Zeile im Quellcode, der des ersten Markers, die Implementierungen sind hier deshalb nur angedeutet. Die Generatorklasse schaut nach Implementierung aller QuickFixes folgendermassen aus:

public class MarkerResolutionGenerator implements IMarkerResolutionGenerator2
{
 
	@Override
	public boolean hasResolutions(IMarker marker) {
 
		try{
			if(marker.getType().equals(XMLCfgBuilder.PUBLIC_NONE_GET_MARKER))
				return true;
			else if(marker.getType().equals(XMLCfgBuilder.NO_DEFAULTVALUE_MARKER))
				return true;
			else if(marker.getType().equals(XMLCfgBuilder.NO_DESCRIPTION_MARKER))
				return true;
			else if(marker.getType().equals(XMLCfgBuilder.INVALID_TYPE_MARKER))
				return true;
			else
				return false;
		}
		catch(Exception ex)
		{
			return false;
		}		
	}
 
	@Override
	public IMarkerResolution[] getResolutions(IMarker marker) {
 
		List<IMarkerResolution> resolutions = new ArrayList<IMarkerResolution>();	
 
		try{
			if(marker.getType().equals(XMLCfgBuilder.PUBLIC_NONE_GET_MARKER))
				resolutions.add(new PublicNoneGetMarkerResolution());
			else if(marker.getType().equals(XMLCfgBuilder.NO_DEFAULTVALUE_MARKER))
				resolutions.add(new NoDefaultValueMarkerResolution());
			else if(marker.getType().equals(XMLCfgBuilder.NO_DESCRIPTION_MARKER))
				resolutions.add(new NoDescriptionMarkerResolution());
			else if(marker.getType().equals(XMLCfgBuilder.INVALID_TYPE_MARKER))
			{
				resolutions.add(new InvalidTypeMarkerResolution("String"));
				resolutions.add(new InvalidTypeMarkerResolution("int"));
				resolutions.add(new InvalidTypeMarkerResolution("boolean"));
				resolutions.add(new InvalidTypeMarkerResolution("double"));
				resolutions.add(new InvalidTypeMarkerResolution("float"));
			}
			else
				return null;			
		}
		catch(Exception ex)
		{
			return null;
		}	
 
		return (IMarkerResolution[])resolutions.toArray(new IMarkerResolution[resolutions.size()]);
	}
 
}


Implementierung Klasse NoDescriptionMarkerResolution

public class NoDescriptionMarkerResolution implements IMarkerResolution2 {
 
	@Override
	public String getDescription() {		
		return "Beschreibungskommentar für Config-Parameter angeben ";
	}
 
 
	@Override
	public Image getImage() {
		// TODO Auto-generated method stub
		return null;
	}
 
	@Override
	public String getLabel() {		
		return "Place a Desciption here";
	}
 
	@Override
	public void run(IMarker marker) {
		IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();		
 
		...//Logik für Einfügen eines Standard-BEschreibungskommentares
 
	}
 
}


Implementierung Klasse NoDefaultValueMarkerResolution

public class NoDefaultValueMarkerResolution implements IMarkerResolution2 {
 
	@Override
	public String getDescription() {		
		return "Default-Value für Config-Parameter angeben ";
	}
 
	@Override
	public Image getImage() {		
		return null;
	}
 
	@Override
	public String getLabel() {		
		return "Set DefaultValue=0";
	}
 
	@Override
	public void run(IMarker marker) {
		IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();		
 
		...//Logik für Einfügen eines Default-Value Kommentares, in Abhängigkeit vom Funktionstyp des Getters
	}
 
	private String getDefaultValueString(String TypeName){
		if(TypeName.equals("String"))
			return "";
		if(TypeName.equals("int"))
			return "0";
		if(TypeName.equals("boolean"))
			return "false";
		if(TypeName.equals("double"))
			return "0.0";
		if(TypeName.equals("float"))
			return "0.0";
 
		return "";			
 
 
	}
}


Implementierung Klasse InvalidTypeMarkerResolution

public class InvalidTypeMarkerResolution implements IMarkerResolution2 {
 
	private String NewTypeName;
 
	public InvalidTypeMarkerResolution(String NewTypeName){
		this.NewTypeName = NewTypeName;		
	}
 
 
	@Override
	public String getDescription() {
		return "Funktionstyp durch " + NewTypeName + " ersetzen"; 
	}
 
	@Override
	public Image getImage() {		
		return null;
	}
 
	@Override
	public String getLabel() {		
		return "Set to " + NewTypeName;
	}
 
	@Override
	public void run(IMarker marker) {
               IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
 
		...//Logik für Änderung der MethodenSignatur mit neuem Rückgabewert
 
	}
 
}


Damit sind die QuickFixes für alle 4 Marker implementiert und können zur Behebung der Probleme aufgerufen werden.

Literatur

Project Builders and Natures Ein Kompakter Überblick
Using markers to tell users about problems and tasks Artikel zur Verwendung von Markern in Eclipse
Eclipse documentation Dokumentation zur Plugin-Programmierung
Eclipse Project Forum zur Plugin-Programmierung
[DA05]The Java Developers Guide to Eclipse Second Edition Jim D'Anjou,Scott Fairbrother,Dan Kehn. ISBN 978-3827322548
[CR09] Clayberg, Eric ; Rubel, Dan: eclipse Plug-ins. 3rd. Addison-Wesley, 2009 The Eclipse Series. ISBN 978–0321553461

Code für Eclipse-Projekte

Den vollständigen Beispielcode git es hier:
Plugin-Projekt

Test-Projekt