Kommandos (Command
)
Standardaufgaben wie Zoomen, Rotieren oder das Aktivieren von AddOns sind
innerhalb der jadice Pakete in jeweils eigenständige Kommandos gekapselt, die von
Aktionen aufgerufen werden können und die die eigentliche Ausführung der gewünschten
Funktionalität übernehmen. Typische Kommandos benötigen zu ihrer Ausführung eine
Anzahl von Objekten, die ihnen über den weiter oben beschriebenen Context
zur
Verfügung gestellt werden. Damit ein Kommando erfolgreich ausgeführt werden kann,
müssen alle von ihm benötigten Objekte im Kontext vorhanden sein. Es ist Aufgabe des
Integrators, dies sicherzustellen.
Alle Kommandos sind Instanzen der Klasse InjectedCommand
. Zur Ausführung
benötigte Objekte werden InjectedCommand
s via Dependeny Injection vollautomatisch
und typsicher zur Verfügung gestellt. Es existieren zwei Arten der Injektion. Zum
einen können Konfigurationswerte und zum anderen können Kontextobjekte dynamisch
bereitgestellt werden. Beide Arten von Dependency Injection werden im Quellcode
durch Aufbringen von Java-Annotationen ermöglicht und im Folgenden
vorgestellt.
Die Parameter-Injection bezieht sich auf Konfigurationswerte, die aus den
commands.properties
stammen. Um einen Parameterwert zur
Verfügung zu stellen, sind folgende Schritte notwendig:
Definition einer Klassenvariablen in der
Command
-Implementation. Das Framework nimmt selbstständig eine gegebenenfalls notwendige Typ-Konvertierung (beispielsweise fürint
-Werte) vor.Erzeugen von public getter/setter Methoden für die Klassenvariable und Aufbringen der Annotation
Parameter
am Setter.Eintragen des neuen Parameters in der
commands.properties
Datei. Die Einträge müssen dem folgenden Schema entsprechen:<command-name>
.param.<parameter-name>
=<parameter-value>
Der zu verwendende Parametername entspricht dem Methodennamen des setters aber ohne das set-Prefix und mit dem ersten Buchstaben klein geschrieben.
Für Parameter, die auf diese Weise angefordert werden, stellt das Framework sicher, dass diese nach Instanziierung der Klasse bereitstehen.
Für Fälle, in denen der Parametername in
commands.properties
nicht aus dem Methodennamen
generiert werden soll, besteht die Möglichkeit, die Annotation
@Parameter
mit einem optionalen Argument
name
zu versehen, das den in der Properties-Datei
verwendeten Namen definiert. Des Weiteren existiert ein Annotations-Argument
optional
, mit dem definiert werden kann, dass das Fehlen
der Parameter-Definition in commands.properties
nicht zu
einem Fehler führen soll. Das Kommando wird dann trotzdem instanziiert und
annotierte Felder behalten ihren in der Klasse definierten Default-Wert.
Die Parameter-Injection funktioniert für folgende Datentypen:
String
, Color
,
enums und die primitiven Datentypen.
Argument Injection (InjectedCommand
)
Die zweite Möglichkeit ist die Argument-Injection. Während Parameter aus den
Properties stammen, beziehen sich Argumente auf im Context
vorhandene Objekte
und werden aus diesem heraus zur Verfügung gestellt.
Das Anfordern von Objekten aus dem Kontext geschieht (analog zur
Parameter-Injektion) durch Anbringen der Java-Annotation Argument
an einer
setter Methode. Das Framework führt einen Aufruf der Methode mit dem ersten im
Kontext gefundenen Objekt des entsprechenden Typs durch. Dies geschieht vor
einem später eventuell erfolgenden Aufruf der
execute()
-Methode.
In realen Szenarien kann es leicht vorkommen, dass der Kontext mehrere Objekte desselben Typs enthält. Anstatt einfach das erste Objekt des gewünschten Typs anzufordern, besteht alternativ dazu die Möglichkeit, sämtliche Objekte eines Typs abzufragen. In diesem Fall werden alle gefundenen Objekte in einer Collection des entsprechenden Typs zurückgegeben. So werden beispielsweise mit dem Code-Fragment
@Argument
public void setSomeStrings(Collection<String> allContextStrings){…};
sämtliche String-Instanzen aus dem Kontext angefordert und injiziert.
In Zusammenhang mit Collection
s existiert eine
Einschränkung der InjectedCommand
s: Es ist nicht möglich, Objekte des Typs
Collection
im Kontext abzulegen und mittels
Argument-Injection auszulesen. Grund hierfür ist die Generic Type Erasure: Zur
Laufzeit sind die durch Generics spezifizierten Typinformationen nicht
vorhanden, sodass eine korrekte Typkonvertierung nicht zuverlässig möglich ist.
Soll eine Ansammlung von Objekten im Kontext abgelegt und injiziert werden, so
empfehlen wir dafür ein dediziertes Fachobjekt zu erstellen, das die Objektmenge
vorhält.
Die Annotation @Argument
bietet verschiedene Optionen
an, die den Injektions-Vorgang beeinflussen können. Wie es schon für Parameter
der Fall war, können auch Argumente als optional markiert werden. Des Weiteren
existiert eine Option boolean allowOtherMatches
, die
festlegt, ob bei der Abfrage nach einem einzelnen Objekt das Vorhandensein
mehrerer gleicher Objekttypen zu einem Fehler führen soll. Die Option
boolean matchSubclasses
legt fest, ob bei der Suche
nach Objekten im Kontext auch Subtypen des angeforderten Typs zurückgeliefert
werden sollen. Eine letzte Option, Class<? extends Object>
match
, definiert nach welchem Objekttyp der Kontext durchsucht
werden soll. Wie aus dem Beispiel bereits hervorging, wird der gewünschte Typ im
Regelfall automatisch erkannt. Dieses zusätzliche Attribut erlaubt es jedoch,
eine nähere Einschränkung – zum Beispiel auf eine bestimmte Subklasse – zu
treffen.
Eine letzte Möglichkeit der Argument-Injizierung bietet die Annotation
@AllArguments
. Diese stellt sämtliche Argumente des
Kontextes zur Verfügung. Im Regelfall geschieht dies über einen Rückgabewert des
Typs Collection<Object>
, wobei zu bedenken ist, dass
die Klasse Context
das Interface Collection
implementiert. Man bekommt daher eigentlich eine Instanz von Context
zurück.
Um an dieser Stelle keine manuellen Typ-Checks und -Konvertierungen durchführen
zu müssen, kann auf der Annotation @AllArguments
die
Option match = Context.class
spezifiziert werden. Ist diese
Option vorhanden, so wird direkt eine Instanz von Context
zurückgegeben.