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.Aufbringen der Annotation
Parameter
an der KlassenvariablenEintragen 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 Namen der Klassenvariablen.
Für Parameter, die auf diese Weise angefordert werden, stellt das Framework sicher, dass diese nach Instanziierung der Klasse bereitstehen.
Die Annotation @Parameter
kann an jeder
Klassenvariablen angebracht werden, unabhängig von deren Sichtbarkeits-Modifier.
Zu beachten ist jedoch an dieser Stelle, dass in manchen Security-Kontexten (zum
Beispiel innerhalb von Applets) nur public
möglich ist, da
der Aufruf von Field.setAccessible(true)
in einer
SecurityException
resultiert.
Es besteht weiterhin auch die Möglichkeit, die Annotation
@Parameter
an einer setter-Methode anzubringen. Der
erwartete Parametername entspricht dann dem Methodennamen ohne das set-Prefix
und mit dem ersten Buchstaben klein geschrieben. Bezüglich
der Methodensichtbarkeit gelten die gleichen Regeln wie für
Klassenvariablen.
Für Fälle, in denen der Parametername in
commands.properties
nicht dem Variablennamen
entsprechen oder 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 Basis-Datentypen (wie int,
float oder boolean).
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 Objekte aus dem Kontext geschieht (analog zur
Parameter-Injektion) durch Anbringen der Java-Annotation Argument
an einer
Klassenvariablen. Das Framework befüllt daraufhin automatisch diese Variable mit
dem ersten im Kontext gefundenen Objekt des entsprechenden Typs. Es wird
garantiert, dass die Variable befüllt ist wenn ein Aufruf der
execute()
-Methode erfolgt.
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
private Collection<String> someStringArgument;
sämtliche String-Instanzen aus dem Kontext angefordert und in das Feld 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.
Wie auch schon bei den Parametern der Fall, ist es auch für Annotationen des
Typs @Argument
möglich, statt einer Klassenvariable die
entsprechende setter-Methode zu annotieren. Auch bezüglich der Java
Sichtbarkeits-Schlüsselworte gilt oben gesagtes.
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.