39.1
Resolver Hooks

Hinweis Resolver haben in QF-Test bereits eine lange Historie. Seit QF-Test Version 2.0 wird das Testen von SWT basierten Anwendungen unterstützt. Das ComponentNameResolver Interface der älteren QF-Test Versionen basierte auf der AWT/Swing Klasse Component und war daher nicht portabel. Es wurde durch das generische NameResolver Interface und die entsprechenden Methoden in der ResolverRegistry ersetzt.

Mit QF-Test Version 3.1 wurden NameResolver und FeatureResolver durch elegantere Versionen ersetzt, die mangels Alternativen NameResolver2 und FeatureResolver2 heißen. Diese moderneren Versionen reichen den standard Namen oder das standard Feature, welche QF-Test selbst ohne Resolver ermittelt, direkt an die Methoden der Resolver weiter, was deren Implementierung deutlich vereinfacht.

Ebenso wurde der alte ItemNameResolver, der nur für Swing verfügbar war, durch den neuen ItemNameResolver2 plus zugehörigem ItemValueResolver2 ersetzt, welche mit allen GUI Engines funktionieren. Zur Rückwärtskompatibilität werden die alten Methoden weiterhin unterstützt. Sie sollten allerdings nicht mehr verwendet werden und sind hier nicht dokumentiert.

39.1.1
Übersicht über die unterstützten Arten von Resolvern

Mit diesem Erweiterungs API können Sie Einfluss darauf nehmen, wie QF-Test Komponenten und Unterelemente erkennt und aufzeichnet. Dies ist ein sehr mächtiges Feature, mit dem Sie volle Kontrolle über das Komponenten-Management von QF-Test erhalten. Und es ist wesentlich einfacher als es aussieht. Mit dem resolvers Modul, welches in Abschnitt 39.1.10 beschrieben wird, können Sie einen kompletten Resolver mit wenigen Zeilen Code implementieren und installieren.

Folgende Arten von Resolvern können verwendet werden:

Es können verschiedene Resolver mit unterschiedlichen Aufgaben erstellt und zur Laufzeit registriert werden. Die jeweiligen Interfaces können direkt durch Java Klassen implementiert werden. Diese Methode würde allerdings dazu führen, dass ein Teil Ihrer Applikation von den QF-Test Klassen abhängig wird. Daher ist es vorzuziehen, die Resolver Interfaces in Jython oder Groovy zu implementieren, ab besten über das in Abschnitt 39.1.10 beschriebene resolvers Modul. Dadurch kann der gesamte Mechanismus strikt vom SUT getrennt bleiben und hat keinerlei Einfluss auf dessen Entwicklungsprozess.

Bevor wir darauf eingehen, wie Resolver registriert (und entfernt) werden, werfen wir zunächst einen Blick auf deren Interfaces.

3.1+39.1.2
Das ClassNameResolver Interface

Das Interface de.qfs.apps.qftest.extensions.ClassNameResolver besteht aus einer einzigen Methode:

 
 
String getClassName(Object element, String name)
Legt den Klassennamen einer Komponente fest.
Parameter
element Das GUI Element dessen Klassenname ermittelt werden soll.
name Der ursprüngliche Klassenname, den QF-Test ohne Resolver verwenden würde.
Rückgabewert Der zu verwendende Klassenname oder null, falls der Resolver das Element nicht behandelt.
 
 

Nachdem QF-Test den Klassennamen einer Komponente ermittelt hat, erhalten die registrierten ClassNameResolver die Chance, diesen zu überschreiben. Der erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis. Sind keine Resolver registriert oder liefern alle Resolver null, wird der ursprüngliche Klassenname benutzt.

Bei Swing und SWT Anwendungen werden Klassen anhand der tatsächlichen Klassenhierarchie überprüft, daher darf der ClassNameResolver nur die tatsächliche Klasse des Elements, oder eine ihrer Basisklassen zurückliefern. Dies kann trotzdem nützlich sein, wenn Sie z.B. die Option Nur Systemklassen aufnehmen nicht aktivieren, für bestimmte Komponenten aber dennoch die Systemklasse oder eine andere generelle Basisklasse aufzeichnen wollen. Swing und SWT Klassen werden nicht zwischengespeichert, der Resolver wird also jedes mal wieder aufgerufen, wenn QF-Test den Klassennamen einer Komponente bestimmt.

Für Webanwendungen verwendet QF-Test eine pseudo Klassenhierarchie, die in Abschnitt 6.4 beschrieben ist. Der Resolver kann hier jeden beliebigen Klassennamen vergeben, den QF-Test dann als eine Unterklasse von der Pseudoklasse des Elements behandelt. Aus Performanzgründen werden bei Webanwendungen die Klassen zwischengespeichert, daher wird der Resolver für jeden DOM Knoten höchstens einmal aufgerufen.

3.1+39.1.3
Das NameResolver2 Interface

Das Interface de.qfs.apps.qftest.extensions.NameResolver2 besteht aus einer einzigen Methode:

 
 
String getName(Object element, String name)
Legt den Namen einer Komponente fest.
Parameter
element Das GUI Element dessen Name ermittelt werden soll.
name Der ursprüngliche Name, den QF-Test ohne Resolver verwenden würde.
Rückgabewert Der zu verwendende Name oder null, falls der Resolver das Element nicht behandelt. Durch Rückgabe des leeren Strings wird der ursprüngliche Name der Komponente unterdrückt.
 
 

Nachdem QF-Test den Namen einer Komponente ermittelt hat, erhalten die registrierten NameResolver2 die Chance, diesen zu überschreiben oder zu unterdrücken. Der erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis. Sind keine Resolver registriert oder liefern alle Resolver null, wird der ursprüngliche Name benutzt.

In einzelnen Fällen kann es sinnvoll sein, den Namen einer Komponente zu unterdrücken, z.B. wenn er nicht eindeutig ist, oder - wesentlich schlimmer - wenn er während der Laufzeit variiert. Um das zu erreichen muss getName den leeren String zurückliefern.

39.1.4
Das IdResolver Interface

Das Interface de.qfs.apps.qftest.extensions.IdResolver besteht aus einer einzigen Methode:

 
 
String getId(DomNode node, String id)
Legt die ID eines DomNode Knotens fest. Die so bestimmte ID wird gespeichert und kann später mittels node.getId() ausgelesen werden. Dagegen ermittelt node.getAttribute("id") immer das ursprüngliche, unmodifizierte "id" Attribut.
Parameter
node Der DomNode Knoten dessen ID ermittelt werden soll.
id Das "id" Attribut, das QF-Test für diesen Knoten ermittelt hat, möglicherweise nach Entfernen der darin enthaltenen Ziffern, abhängig von der Option Alle Ziffern aus 'id' Attributen eliminieren. Um den Resolver auf Basis des ursprünglichen "id" Attributs zu implementieren, ermitteln Sie dieses einfach via node.getAttribute("id").
Rückgabewert Die ID oder null, falls keine ID festgelegt werden kann. Durch Rückgabe des leeren Strings wird der eigentliche ID der Komponente unterdrückt.
 
 

Wenn QF-Test die Knoten einer Webseite registriert, wird das "id" Attribut dieser Knoten gespeichert. Da vieler Webseiten automatisch generierte IDs verwenden, ist oft eine Modifikation dieser IDs nötig, z.B das Entfernen von Ziffern, welches QF-Test abhängig von der Option Alle Ziffern aus 'id' Attributen eliminieren automatisch vornimmt.

Das "id" Attribut eines Knotens kann an verschiedenen Stellen wieder auftauchen, vor allem im Attribut 'Name' des Knotens (abhängig von der Option 'Id' Attribut als Name verwenden falls "eindeutig genug"), im Attribut 'Merkmal' und im Attribut 'Weitere Merkmale'. Daher ist es viel effizienter, einmalig die ID mittels eines IdResolvers zu verändern, als getrennte Name-, Feature- und ExtraFeatureResolvers zu implementieren. Noch wichtiger ist der Umstand, dass die Veränderung der ID eines Knotens großen Einfluss auf die Eindeutigkeit dieser ID haben kann. Der Mechanismus zum Ermitteln von Namen auf Basis der ID nimmt darauf Rücksicht, so dass ein IdResolver auch nicht eindeutige IDs liefern darf. Ein NameResolver2 muss dagegen eindeutige Namen liefern.

3.1+39.1.5
Das FeatureResolver2 Interface

Das Interface de.qfs.apps.qftest.extensions.FeatureResolver2 besteht aus einer einzigen Methode:

 
 
String getFeature(Object element, String feature)
Legt das Merkmal einer Komponente fest.
Parameter
element Das GUI Element dessen Merkmal ermittelt werden soll.
feature Das ursprüngliche Merkmal, das QF-Test ohne Resolver verwenden würde.
Rückgabewert Das zu verwendende Merkmal oder null, falls der Resolver das Element nicht behandelt. Durch Rückgabe des leeren Strings wird das ursprüngliche Merkmal der Komponente unterdrückt.
 
 

Nachdem QF-Test das Merkmal einer Komponente ermittelt hat, erhalten die registrierten FeatureResolver2 die Chance, dieses zu überschreiben oder zu unterdrücken. Der erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis. Sind keine Resolver registriert oder liefern alle Resolver null, wird das ursprüngliche Merkmal benutzt.

Um das Merkmal einer Komponente zu unterdrücken, muss getFeature den leeren String zurückliefern.

39.1.6
Das ExtraFeatureResolver Interface

Das Interface de.qfs.apps.qftest.extensions.ExtraFeatureResolver besteht aus einer einzigen Methode:

 
 
ExtraFeatureSet getExtraFeatures(Object element, ExtraFeatureSet features)
Legt weitere Merkmale einer Komponente fest.
Parameter
element Das GUI Element dessen weitere Merkmale ermittelt werden sollen.
features Ein Satz von weiteren Merkmale, die QF-Test selbst festgelegt hat, ein leerer Satz falls es keine solchen Merkmale gibt. Dieser Satz kann modifiziert oder ignoriert und durch einen anderen ersetzt werden.
Rückgabewert Der modifizierte original Satz von Merkmalen oder ein neuer Satz, falls der Resolver das GUI Element behandelt, andernfalls null. Die ursprünglichen Merkmale können durch Rückgabe eines leeren Satzes unterdrückt werden.
 
 

Nachdem QF-Test die weiteren Merkmale eines GUI Elements ermittelt hat, erhalten die registrierten ExtraFeatureResolver die Chance, diese zu modifizieren. Im Gegensatz zu anderen Resolvern bricht QF-Test nicht ab, sobald ein ExtraFeatureResolver einen nicht-null Wert zurückliefert, sondern übernimmt diesen Wert als Parameter für den nächsten Resolver. Hierdurch können mehrere ExtraFeatureResolver registriert werden, die jeweils ein spezielles Merkmal behandeln. Sind keine Resolver registriert oder liefern alle Resolver null, macht QF-Test mit dem ursprünglich ermittelten Satz von Merkmalen weiter.

Um die getExtraFeatures Methode implementieren zu können, müssen Sie natürlich das API der Beteiligten Klassen ExtraFeature und ExtraFeatureSet kennen. Diese werden im Folgenden Beschrieben. Ein Beispiel zur Implementierung finden Sie in Beispiel 39.6.

Ein Objekt der Klasse de.qfs.apps.qftest.shared.data.ExtraFeature repräsentiert ein weiteres Merkmal eines GUI Elements, bestehend aus Name und Wert, sowie Informationen darüber, ob für das Feature ein Treffer erwartet wird, ob der Wert ein regulärer Ausdruck ist und ob die Aussage negiert werden soll. Für mögliche Stati definiert die Klasse die Konstanten STATE_IGNORE, STATE_SHOULD_MATCH und STATE_MUST_MATCH.

 
 
ExtraFeature (String name, String value)
Erzeugt ein neues ExtraFeature mit Status STATE_IGNORE.
Parameter
nameDer Name des ExtraFeatures.
valueDer Wert des ExtraFeatures.
 
ExtraFeature (int state, String name, String value)
Erzeugt ein neues ExtraFeature mit gegebenem Status.
Parameter
state Der Status des ExtraFeatures. Mögliche Werte sind STATE_IGNORE, STATE_SHOULD_MATCH, STATE_MUST_MATCH.
nameDer Name des ExtraFeatures.
valueDer Wert des ExtraFeatures.
 
int getState()
Liefert den Status des ExtraFeatures.
RückgabewertDer Status des ExtraFeatures.
 
void setState(int state)
Setzt den Status des ExtraFeatures.
Parameter
stateDer zu setzende Status.
 
String getName()
Liefert den Namen des ExtraFeatures.
RückgabewertDes Name des ExtraFeatures.
 
void setName(String name)
Setze den Namen des ExtraFeatures.
Parameter
nameDer zu setzende Name.
 
String getValue()
Liefert den Wert des ExtraFeatures.
RückgabewertDer Wert des ExtraFeatures.
 
void setValue(String value)
Setzt den Wert des ExtraFeatures.
Parameter
valueDer zu setzende Wert.
 
boolean getRegexp()
Liefert den regexp Status des ExtraFeatures.
Rückgabewertder regexp Status des ExtraFeatures.
 
void setRegexp(boolean regexp)
Setzt den regexp Status des ExtraFeatures.
Parameter
regexpDer zu setzende regexp Status.
 
boolean getNegate()
Liefert den negate Status des ExtraFeatures.
Rückgabewertder negate Status des ExtraFeatures.
 
void setNegate(boolean negate)
Setzt den negate Status des ExtraFeatures.
Parameter
negateDer zu setzende negate Status.
 
 

Die Klasse de.qfs.apps.qftest.shared.data.ExtraFeatureSet bündelt ExtraFeatures in einen Satz:

 
 
ExtraFeatureSet ()
Erzeugt ein neues, leeres ExtraFeatureSet.
 
void add(ExtraFeature extraFeature)
Fügt dem Satz ein ExtraFeature hinzu. Existiert schon ein ExtraFeature mit dem selben Namen, wird dieses ersetzt.
Parameter
extraFeatureDas hinzuzufügende ExtraFeature.
 
ExtraFeature get(String name)
Liefert ein ExtraFeature des Satzes.
Parameter
nameDer Name des abzufragenden ExtraFeatures.
Rückgabewert Das ExtraFeature oder null, falls kein ExtraFeature unter diesem Namen im Satz gespeichert ist.
 
ExtraFeature remove(String name)
Entfernt ein ExtraFeature aus dem Satz.
Parameter
nameDer Name des zu entfernenden ExtraFeatures.
Rückgabewert Das entfernt ExtraFeature oder null, falls kein ExtraFeature unter diesem Namen im Satz gespeichert war.
 
ExtraFeature[] toArray()
Liefert alle ExtraFeatures des Satzes.
RückgabewertEin Array mit allen ExtraFeatures, nach Namen sortiert.
 
 
3.1+39.1.7
Das ItemNameResolver Interface

Hinweis Sofern Sie nicht das in Abschnitt 39.1.10 beschriebene resolvers Modul nutzen, muss ein ItemNameResolver2 nicht bei der ResolverRegistry registriert werden, sondern bei der ItemRegistry, wie in Abschnitt 39.2.4 beschrieben.

Das Interface de.qfs.apps.qftest.extensions.ItemNameResolver2 besteht aus einer einzigen Methode:

 
 
String getItemName(Object element, Object item, String name)
Legt den Namen zur textuellen Repräsentation des Index eines Unterelements einer komplexen Komponente fest.
Parameter
element Das GUI Element zu dem das Unterelement gehört.
item Das Unterelement, dessen Name ermittelt wird. Sein Typ hängt von der Art des GUI Elements und der registrierten ItemResolver ab, wie in Abschnitt 39.2.5 beschrieben.
name Der ursprüngliche Name, den QF-Test ohne Resolver verwenden würde.
RückgabewertDer Name oder null, falls der Resolver dieses Element oder Unterelement nicht behandelt.
 
 

Nachdem QF-Test einen Namen für den Index eines Unterelements ermittelt hat, erhalten die registrierten ItemNameResolver2 die Chance, diesen zu überschreiben. Der erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis. Sind keine Resolver registriert oder liefern alle Resolver null, wird der ursprüngliche Name benutzt.

3.1+39.1.8
Das ItemValueResolver Interface

Hinweis Sofern Sie nicht das in Abschnitt 39.1.10 beschriebene resolvers Modul nutzen, muss ein ItemValueResolver2 nicht bei der ResolverRegistry registriert werden, sondern bei der ItemRegistry, wie in Abschnitt 39.2.4 beschrieben.

Das Interface de.qfs.apps.qftest.extensions.ItemValueResolver2 besteht aus einer einzigen Methode:

 
 
String getItemValue(Object element, Object item, String value)
Legt den Wert der eines Unterelements einer komplexen Komponente fest, wie er für 'Check Text' oder 'Text auslesen' Knoten verwendet wird.
Parameter
element Das GUI Element zu dem das Unterelement gehört.
item Das Unterelement, dessen Wert ermittelt wird. Sein Typ hängt von der Art des GUI Elements und der registrierten ItemResolver ab, wie in Abschnitt 39.2.5 beschrieben.
value Der ursprüngliche Wert, den QF-Test ohne Resolver verwenden würde.
RückgabewertDer Wert oder null, falls der Resolver dieses Element oder Unterelement nicht behandelt.
 
 

Nachdem QF-Test einen Wert für ein Unterelements ermittelt hat, erhalten die registrierten ItemValueResolver2 die Chance, diesen zu überschreiben. Der erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis. Sind keine Resolver registriert oder liefern alle Resolver null, wird der ursprüngliche Wert benutzt.

39.1.9
Das TreeTableResolver Interface

Hinweis Dieses Interface ist für SWT irrelevant. Mehrspaltige Bäume in SWT werden von QF-Test automatisch unterstützt.

Ein de.qfs.apps.qftest.extensions.TreeTableResolver muss folgende beiden Methoden implementieren:

 
 
JTree getTree(JTable table)
Ermittelt die JTree Komponente mit deren Hilfe eine TreeTable implementiert ist.
Parameter
table Die Tabelle für die der Baum ermittelt werden soll.
Rückgabewert Der Baum oder null, falls es sich um eine normale Tabelle handelt.
 
int getTreeColumn(JTable table)
Ermittelt den Index der Spalte des Baums in einer TreeTable. Die meisten Implementierungen haben den Baum in der ersten Spalte. In diesem Fall muss 0 zurückgegeben werden.
Parameter
table Die Tabelle für die der Spaltenindex des Baum ermittelt werden soll.
Rückgabewert Der Spaltenindex oder -1, falls es sich um eine normale Tabelle handelt. Der Spaltenindex muss immer in Model-Koordinaten geliefert werden, nicht in View-Koordinaten.
 
 

Die meisten TreeTableResolver sind trivial zu implementieren. Das folgende Beispiel in Jython genügt bereits für die org.openide.explorer.view.TreeTable Komponente der populären netBeans IDE, vorausgesetzt, dass der Resolver für die TreeTable Klasse registriert wird.

class TTResolver(TreeTableResolver):
    def getTree(self, table):
        return table.getCellRenderer(0,0)

    def getTreeColumn(self, table):
        return 0
Beispiel 39.1:  TreeTableResolver für die netBeans IDE.
39.1.10
Vereinfachte Erstellung von Resolvern mit dem resolvers Modul

Alle Arten von Resolvern können in Java, Jython oder Groovy implementiert werden und müssen dann bei der ResolverRegistry angemeldet werden. Bevor wir im folgenden Abschnitt diesen relativ komplexen Prozess im Detail beschreiben, zeigen wir zunächst an Hand von Beispielen, wie Resolver sehr einfach mit Hilfe des resolvers Moduls erstellt werden können, welches mit QF-Test geliefert wird. Diese Beispiele sollten für den normalen Gebrauch ausreichen. Nur wenn Sie im Detail verstehen wollen wie das resolvers Modul selbst funktioniert, oder wenn Sie Resolver in Java implementieren wollen, sollten Sie auch die folgenden Abschnitte lesen.

Abgesehen von den komplexeren ItemResolver und Checker Interfaces, die in den folgenden Abschnitten beschrieben werden, besteht ein Resolver nur aus einer Methode, die implementiert werden muss (eineinhalb im Fall des TreeTableResolvers). Auf Java Ebene werden Resolver dadurch kompliziert, dass diese Methode als Interface definiert und von einer Klasse implementiert werden muss. Von dieser Klasse muss eine Instanz erzeugt und bei QF-Test registriert werden. Wenn es nicht auf Anhieb funktioniert, muss die Instanz deregistriert werden, bevor eine neue Klasse erstellt und eine Instanz davon registriert werden kann. Andernfalls könnte es zu Konflikten zwischen den beiden Versionen des Resolvers kommen. Dazu kommt noch Code für die Fehlerbehandlung, insgesamt also ein vielfaches an Ballast im Vergleich zur eigentlichen Substanz.

Zum Glück sind Methoden in Jython ebenso wie Closures in Groovy vollwertige Objekte und der überschüssige Code ist im Prinzip für alle Resolver gleich. Dadurch ist es möglich, die lästigen Teile aus dem Weg und in ein Modul zu verfrachten, und sich auf die eigentliche Aufgabe zu konzentrieren. Das folgende Beispiel zeigt einen NameResolver für JMenuItems der den Text eines Menüeintrags als Name der Komponente definiert sofern es keinen eigenen Namen besitzt:

def getName(menuItem, name):
    if not name:
        return menuItem.getLabel()
resolvers.addNameResolver2("menuItems", getName,
                           "javax.swing.JMenuItem")
Beispiel 39.2:  Einfacher Jython NameResolver2 für JMenuItems

Wie Sie sehen bedarf es ganzer vier Zeilen Code, um das zu bewerkstelligen. Die ersten drei Zeilen definieren die getName Methode für den Resolver. Die vierte Zeile ruft die addNameResolver2 Funktion im resolvers Modul auf, welches immer automatisch in Jython 'SUT Skript' Knoten verfügbar ist. Die drei Parameter sind ein Name zur Identifikation des Resolvers, die eigentliche Methode und ein oder mehrere optionale Ziele für den Resolver, in diesem Fall die Klasse von Komponenten für die er registriert werden soll.

Probieren Sie es aus! Kopieren Sie das obige Beispiel in einen 'SUT Skript' Knoten und führen Sie diesen aus. Falls Ihre Anwendung auf SWT basiert, ersetzen Sie getLabel() durch getText() und javax.swing.JMenuItem durch org.eclipse.swt.MenuItem. Nehmen Sie dann einige Menü Aktionen in eine neue, leere Testsuite auf. Sie werden sehen, dass alle Komponenten ohne eigenen Namen für Menüeinträge nun einen Namen entsprechend ihrer Beschriftung erhalten. Falls setName in Ihrer Anwendung nicht verwendet wird und die Menü-Bezeichner weitgehend statisch sind, kann das sogar eine recht nützliche Sache sein. Um den Resolver permanent zu installieren, verschieben Sie den 'SUT Skript' Knoten direkt hinter den 'Warten auf Client' Knoten in Ihrer Sequenz zum Start des SUT.

Das gleiche kann in Groovy mittels des resolvers Objekts erreicht werden, welches ebenfalls immer automatisch verfügbar ist und das gleiche API besitzt. Es erwartet als Parameter eine Closure um daraus den Resolver zu erstellen:

def getName = {menuItem, name ->
    if (name == null) {
        return menuItem.getLabel()
    }
}
resolvers.addNameResolver2("menuItems", getName,
                           "javax.swing.JMenuItem")
Beispiel 39.3:  Einfacher Groovy NameResolver2 für JMenuItems

Ein Resolver kann gleichzeitig für mehrere Klassen von Elementen registriert werden:

def getName(com, name):
    return com.getLabel()
resolvers.addNameResolver2("labels", getName, "javax.swing.JLabel",
                           "javax.swing.AbstractButton")
Beispiel 39.4:  Registrieren eines NameResolver2 für mehrere Klassen

Die anderen Resolver funktionieren genauso. Es folgt ein Beispiel für einen ItemNameResolver2, der die ID einer JTable Spalte für den

def getColumnId(table, item, name):
    if item[1] < 0:  # whole column addressed
    id = table.getColumnModel().getColumn(i).getIdentifier()
    if id:
        return str(id)
resolvers.addItemNameResolver("tableColumnId", getColumnId,
                              "javax.swing.table.TableColumn")
Beispiel 39.5:  Ein ItemNameResolver für JTable Spalten

FeatureResolver und ExtraFeatureResolver werden genauso wie NameResolver erstellt, wobei ExtraFeatureResolver ein wenig komplexer sind, aber nicht viel, wie das folgende Beispiel zeigt:

from de.qfs.apps.qftest.shared.data import ExtraFeature, ExtraFeatureSet
def getExtraFeatures(node, features):
    features.add(ExtraFeature(ExtraFeature.STATE_SHOULD_MATCH,
                              "myname", "myvalue"))
    return features
resolvers.addExtraFeatureResolver("silly example", getExtraFeatures,
                                  "INPUT")
Beispiel 39.6:   ExtraFeatureResolver der ein weiteres Feature für INPUT Knoten erstellt

TreeTableResolvers für TreeTables in Swing werden etwas anders erstellt und registriert:

def getTree(table):
    return table.getTree()
def getColumn(table):
    return 0
resolvers.addTreeTableResolver("treeTable", getTree, getColumn,
                               "my.package.TreeTable")
Beispiel 39.7:  TreeTableResolver für Swing TreeTable mit optionaler getColumn Methode

Das obige beispiel zeigt einen typischen TreeTableResolver. Da praktisch alle TreeTables den Baum in der ersten Spalte der Tabelle darstellen, ist die getColumn Methode optional. Wird keine übergeben, wird automatisch eine default Implementierung für die erste Spalte erstellt:

def getTree(table):
    return table.getTree()
resolvers.addTreeTableResolver("treeTable", getTree, None,
                               "my.package.TreeTable")
Beispiel 39.8:  Vereinfachter TreeTableResolver

Falls keine dedizierte getTree Methode vorhanden ist, hilft meist der CellRenderer der Spalte, die den Baum enthält (typischerweise 0), da dieser oft von JTree abgeleitet ist.

def getTree(table):
    return table.getCellRenderer(0,0)
resolvers.addTreeTableResolver("treeTable", getTree,
                               "my.package.TreeTable")
Beispiel 39.9:  Einfacher TreeTableResolver, der die getCellRenderer Methode nutzt

Es folgt eine vollständige Aufstellung der Funktionen des resolvers Moduls:

 
 
addNameResolver2(String name, Method method, Object target=None, ...):
Registriert einen NameResolver für die angegebenen Ziele, basierend auf der angegebenen Funktion. Falls bereits ein Resolver unter dem angegebenen Namen registriert war, wird dieser zunächst deregistriert.
Parameter
name Der Name unter dem der Resolver registriert werden soll.
method Die Funktion, die die getName() Methode des Resolvers implementiert.
target Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für jedes Ziel gibt es folgende Varianten:
  • Eine individuelle Komponente
  • Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
 
addIdResolver(String name, Method method, Object target=None, ...):
Registriert einen IdResolver für die angegebenen Ziele, basierend auf der angegebenen Funktion. Falls bereits ein Resolver unter dem angegebenen Namen registriert war, wird dieser zunächst deregistriert.
Parameter
name Der Name unter dem der Resolver registriert werden soll.
method Die Funktion, die die getId() Methode des Resolvers implementiert.
target Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für jedes Ziel gibt es folgende Varianten:
  • Eine individuelle Komponente
  • Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
 
addFeatureResolver2(String name, Method method, Object target=None, ...):
Registriert einen FeatureResolver2 für die angegebenen Ziele, basierend auf der angegebenen Funktion. Falls bereits ein Resolver unter dem angegebenen Namen registriert war, wird dieser zunächst deregistriert.
Parameter
name Der Name unter dem der Resolver registriert werden soll.
method Die Funktion, die die getFeature() Methode des Resolvers implementiert.
target Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für jedes Ziel gibt es folgende Varianten:
  • Eine individuelle Komponente
  • Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
 
addExtraFeatureResolver(String name, Method method, Object target=None, ...):
Registriert einen ExtraFeatureResolver für die angegebenen Ziele, basierend auf der angegebenen Funktion. Falls bereits ein Resolver unter dem angegebenen Namen registriert war, wird dieser zunächst deregistriert.
Parameter
name Der Name unter dem der Resolver registriert werden soll.
method Die Funktion, die die getExtraFeatures() Methode des Resolvers implementiert.
target Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für jedes Ziel gibt es folgende Varianten:
  • Eine individuelle Komponente
  • Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
 
addItemNameResolver2(String name, Method method, Object target=None, ...):
Registriert einen ItemNameResolver2 für die angegebenen Ziele, basierend auf der angegebenen Funktion. Falls bereits ein Resolver unter dem angegebenen Namen registriert war, wird dieser zunächst deregistriert.
Parameter
name Der Name unter dem der Resolver registriert werden soll.
method Die Funktion, die die getItemName() Methode des Resolvers implementiert.
target Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für jedes Ziel gibt es folgende Varianten:
  • Eine individuelle Komponente
  • Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
 
addItemValueResolver2(String name, Method method, Object target=None, ...):
Registriert einen ItemValueResolver2 für die angegebenen Ziele, basierend auf der angegebenen Funktion. Falls bereits ein Resolver unter dem angegebenen Namen registriert war, wird dieser zunächst deregistriert.
Parameter
name Der Name unter dem der Resolver registriert werden soll.
method Die Funktion, die die getItemValue() Methode des Resolvers implementiert.
target Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für jedes Ziel gibt es folgende Varianten:
  • Eine individuelle Komponente
  • Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
 
addTreeTableResolver(String name, Method getTable, Method getColumn=None, Object target=None):
Registriert einen TreeTableResolver für die angegebenen Ziele, basierend auf den angegebenen Funktionen. Falls bereits ein Resolver unter dem angegebenen Namen registriert war, wird dieser zunächst deregistriert.
Parameter
name Der Name unter dem der Resolver registriert werden soll.
getTable Die Funktion, die die getTable() Methode des Resolvers implementiert.
getColumn Die optionale Funktion, die die getColumn() Methode des Resolvers implementiert.
target Ein optionales Ziel für das der Resolver registriert werden soll. Für dieses Ziel gibt es folgende Varianten:
  • Eine individuelle Komponente
  • Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
 
addClassNameResolver(String name, Method method, Object target=None, ...):
Registriert einen ClassNameResolver für die angegebenen Ziele, basierend auf der angegebenen Funktion. Falls bereits ein Resolver unter dem angegebenen Namen registriert war, wird dieser zunächst deregistriert.
Parameter
name Der Name unter dem der Resolver registriert werden soll.
method Die Funktion, die die getClassName() Methode des Resolvers implementiert.
target Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für jedes Ziel gibt es folgende Varianten:
  • Eine individuelle Komponente
  • Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
 
removeResolver(String name)
Deregistriert einen Resolver von allen Zielen, für die er registriert war.
Parameter
nameDer Name, unter dem der Resolver registriert war.
 
 
39.1.11
Die ResolverRegistry

Die Singleton Klasse de.qfs.apps.qftest.extensions.ResolverRegistry ist die zentrale Agentur für die Registrierung und das Entfernen von Resolvern.

Alle Arten von Resolvern können wahlweise für individuelle Komponenten oder für Klassen registriert werden. Resolver für individuelle Komponenten werden nur aufgerufen, wenn ihre Information für genau diese Komponente benötigt wird. Resolver, die für eine Klasse registriert sind, werden für alle Objekte dieser oder einer davon abgeleiteten Klasse aufgerufen.

NameResolver, FeatureResolver und TreeTableResolver können auch global registriert werden, so dass sie für jeden zu ermittelnden Namen und jedes Merkmal aufgerufen werden. Dies ist äquivalent zu - aber effektiver als - eine Registrierung für die Klasse java.lang.Object.

Sind mehrere Resolver global, für ein bestimmtes Objekt oder eine bestimmte Klasse registriert, wird der zuletzt registrierte Resolver zuerst aufgerufen.

Das API der ResolverRegistry enthält keine großen Überraschungen:

 
 
static ResolverRegistry instance()
Es gibt immer nur ein einziges ResolverRegistry Objekt und diese Methode ist der einzige Weg, Zugriff auf diese Singleton Instanz erlangen.
RückgabewertDie ResolverRegistry Singleton Instanz.
 
static String getElementName(Object element)
Diese statische Methode sollte an Stelle von com.getName() bzw. widget.getData() von Resolvern verwendet werden, die abhängig von bestehenden Namen von Komponenten operieren, mit Ausnahme des NameResolver2, der diesen Namen übergeben bekommt. Diese Methode behandelt triviale oder für AWT/Swing spezielle Namen gesondert.
Parameter
element Das GUI Element dessen Name ermittelt werden soll.
Rückgabewert Der Name der Komponente oder null, falls ein trivialer oder spezieller Name unterdrückt wird.
 
static boolean isInstance(Object object, String className)
Diese statische Methode sollte anstelle von instanceof (oder isinstance() in Jython) verwendet werden. Sie prüft, ob ein Objekt eine Instanz der gegebenen Klasse ist. Diese Prüfung wird nicht wie üblich über Reflection, sondern auf der Basis von Klassennamen durchgeführt. Dadurch werden Probleme mit verschiedenen ClassLoadern vermieden und die Klasse muss gar nicht erst importiert werden.
Parameter
objectDas zu prüfende Objekt.
classNameDer Name der Klasse auf die getestet wird.
Rückgabewert True falls das Objekt eine Instanz der Klasse ist.
 
void registerNameResolver2(Object element, NameResolver2 resolver)
Registriert einen NameResolver2 für eine spezifische Komponente. Der Resolver beeinträchtigt nicht die Garbage Collection und wird automatisch entfernt, wenn die Komponente nicht mehr erreichbar ist.
Parameter
element Das GUI Element für das registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterNameResolver2(Object element, NameResolver2 resolver)
Entfernt einen NameResolver2 für eine spezifische Komponente.
Parameter
element Das GUI Element für das entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerNameResolver2(String clazz, NameResolver2 resolver)
Registriert einen NameResolver2 für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterNameResolver2(String clazz, NameResolver2 resolver)
Entfernt einen NameResolver2 für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerNameResolver2(NameResolver2 resolver)
Registriert einen globalen NameResolver2.
Parameter
resolver Der zu registrierende Resolver.
 
void unregisterNameResolver2(NameResolver2 resolver)
Entfernt einen globalen NameResolver2.
Parameter
resolver Der zu entfernende Resolver.
 
void registerIdResolver(Object element, IdResolver resolver)
Registriert einen IdResolver für eine spezifische Komponente. Der Resolver beeinträchtigt nicht die Garbage Collection und wird automatisch entfernt, wenn die Komponente nicht mehr erreichbar ist.
Parameter
element Das GUI Element für das registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterIdResolver(Object element, IdResolver resolver)
Entfernt einen IdResolver für eine spezifische Komponente.
Parameter
element Das GUI Element für das entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerIdResolver(String clazz, IdResolver resolver)
Registriert einen IdResolver für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterIdResolver(String clazz, IdResolver resolver)
Entfernt einen IdResolver für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerIdResolver(IdResolver resolver)
Registriert einen globalen IdResolver.
Parameter
resolver Der zu registrierende Resolver.
 
void unregisterIdResolver(IdResolver resolver)
Entfernt einen globalen IdResolver.
Parameter
resolver Der zu entfernende Resolver.
 
void registerFeatureResolver2(Object element, FeatureResolver2 resolver)
Registriert einen FeatureResolver2 für eine spezifische Komponente. Der Resolver beeinträchtigt nicht die Garbage Collection und wird automatisch entfernt, wenn die Komponente nicht mehr erreichbar ist.
Parameter
element Das GUI Element für das registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterFeatureResolver2(Object element, FeatureResolver2 resolver)
Entfernt einen FeatureResolver2 für eine spezifische Komponente.
Parameter
element Das GUI Element für das entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerFeatureResolver2(String clazz, FeatureResolver2 resolver)
Registriert einen FeatureResolver2 für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterFeatureResolver2(String clazz, FeatureResolver2 resolver)
Entfernt einen FeatureResolver2 für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerFeatureResolver2(FeatureResolver2 resolver)
Registriert einen globalen FeatureResolver2.
Parameter
resolver Der zu registrierende Resolver.
 
void unregisterFeatureResolver2(FeatureResolver2 resolver)
Entfernt einen globalen FeatureResolver2.
Parameter
resolver Der zu entfernende Resolver.
 
void registerExtraFeatureResolver(Object element, ExtraFeatureResolver resolver)
Registriert einen ExtraFeatureResolver für eine spezifische Komponente. Der Resolver beeinträchtigt nicht die Garbage Collection und wird automatisch entfernt, wenn die Komponente nicht mehr erreichbar ist.
Parameter
element Das GUI Element für das registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterExtraFeatureResolver(Object element, ExtraFeatureResolver resolver)
Entfernt einen ExtraFeatureResolver für eine spezifische Komponente.
Parameter
element Das GUI Element für das entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerExtraFeatureResolver(String clazz, ExtraFeatureResolver resolver)
Registriert einen ExtraFeatureResolver für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterExtraFeatureResolver(String clazz, ExtraFeatureResolver resolver)
Entfernt einen ExtraFeatureResolver für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerExtraFeatureResolver(ExtraFeatureResolver resolver)
Registriert einen globalen ExtraFeatureResolver.
Parameter
resolver Der zu registrierende Resolver.
 
void unregisterExtraFeatureResolver(ExtraFeatureResolver resolver)
Entfernt einen globalen ExtraFeatureResolver.
Parameter
resolver Der zu entfernende Resolver.
 
void registerTreeTableResolver(Object element, TreeTableResolver resolver)
Registriert einen TreeTableResolver für eine spezifische Komponente. Der Resolver beeinträchtigt nicht die Garbage Collection und wird automatisch entfernt, wenn die Komponente nicht mehr erreichbar ist.
Parameter
element Das GUI Element für das registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterTreeTableResolver(Object element, TreeTableResolver resolver)
Entfernt einen TreeTableResolver für eine spezifische Komponente.
Parameter
element Das GUI Element für das entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerTreeTableResolver(String clazz, TreeTableResolver resolver)
Registriert einen TreeTableResolver für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die registriert wird.
resolver Der zu registrierende Resolver.
 
void unregisterTreeTableResolver(String clazz, TreeTableResolver resolver)
Entfernt einen TreeTableResolver für eine spezifische Komponenten Klasse.
Parameter
clazz Der Name der Klasse für die entfernt wird.
resolver Der zu entfernende Resolver.
 
void registerTreeTableResolver(TreeTableResolver resolver)
Registriert einen globalen TreeTableResolver.
Parameter
resolver Der zu registrierende Resolver.
 
void unregisterTreeTableResolver(TreeTableResolver resolver)
Entfernt einen globalen TreeTableResolver.
Parameter
resolver Der zu entfernende Resolver.
 
void unregisterResolvers(Object element)
Entfernt alle Resolver für eine spezifische Komponente.
Parameter
element Das GUI Element für das entfernt wird.
 
void unregisterResolvers(String clazz)
Entfernt alle Resolver für eine Komponenten-Klasse.
Parameter
clazz Der Name der Klasse für die entfernt wird.
 
 
39.1.12
Fehlerbehandlung

Alle Exceptions, die während der Ausführung eines Resolvers auftreten, werden von der ResolverRegistry abgefangen. Es wird allerdings nur eine kurze Meldung und kein Stacktrace ausgegeben, weil insbesondere globale Resolver sehr oft aufgerufen werden können. Somit würde ein Resolver, der einen Bug hat, durch die Ausgabe von Stacktraces für jeden Fehler das Client Terminal überfluten. Daher sollten Resolver ihre eigenen Fehlerbehandlungsroutinen enthalten. Dabei können zwar immer noch extrem viele Ausgaben erzeugt werden, aber speziell für Skripte sind diese wenigstens hilfreicher als Java Stacktraces.