Datentypen & Aufzählungen in Faktor-IPS

Verwendung von Java-Klassen als Datentyp

Neben den vordefinierten Datentypen können auch Java-Klassen zur Verwendung als Datentyp registriert werden. Als Beispiel soll uns dabei eine Klasse IsoDate dienen, die ein Datum auf Basis des im ISO-Standard 8601 definierten Formats (YYYY-MM-DD) darstellt. Im Gegensatz zu den Klassen Date und GregorianCalendar soll die Klasse wirklich ein Datum und keinen Zeitpunkt darstellen. Der folgende Sourcecode stellt eine minimale Implementierung dieser Idee dar. Damit die Klasse IsoDate auch wirklich das Value Object Pattern realisiert, müssen natürlich noch die Methoden equals() und hashCode() entsprechend überschrieben werden. Auf die Darstellung wird hier verzichtet. Mit Eclipse können diese Methoden aber über das Menü SourceGenerate hashCode() and equals() leicht generiert werden.

Wenn Sie das Beispiel im Detail durchführen wollen, dann erzeugen Sie zunächst ein neues Java-Projekt (es braucht kein Faktor-IPS Projekt zu sein!) und legen die Klasse IsoDate im Package enums an. Nehmen Sie dieses neue Projekt in den Java Buildpath Ihres ersten Projektes auf. Die Begründung wieso die Klasse nicht im selben Projekt liegen darf, erfolgt weiter unten.

public class IsoDate {
    private int year;
    private int month;
    private int day;

    public IsoDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public int getMonth() {
        return month;
    }

    public int getDay() {
        return day;
    }
    public String toString() {
        String m = month < 10 ? "0" + month : "" + month;
        String d = day < 10 ? "0" + day : "" + day;
        return year + "-" + m + "-" + d;
    }
}

Bevor wir uns damit beschäftigen wie wir IsoDate als Datentyp registrieren können, betrachten wir, was für Anforderungen Faktor-IPS an einen Datentyp hat. Öffnen wir hierzu noch einmal den Dialog für das Attribut beitrag. Da wir Double aus der Liste der Datentypen entfernt haben, wählen wir jetzt Money als Datentyp. Wechseln Sie nun auf die zweite Tabseite und geben als Defaultwert 42 ein. Faktor-IPS erlaubt dabei nur die Eingabe von gültigen Zeichen für vordefinierte Datentypen. Beim verlassen des Eingabefelds wird die Zahl 42 automatisch auf 42,00 € geändert.

Bei selbst definierten Datentypen muss Faktor-IPS prüfen können, ob ein gegebener String einen gültigen Wert des Datentyps darstellt.

Der Codegenerator erzeugt folgenden Code für das Attribut:

import org.faktorips.values.Money;

    @IpsDefaultValue("beitrag")
    public static final Money DEFAULT_VALUE_FOR_BEITRAG = Money.valueOf("42.00 EUR");

    private Money beitrag = DEFAULT_VALUE_FOR_BEITRAG;

    @IpsAttribute(name = "beitrag", kind = AttributeKind.CHANGEABLE, valueSetKind = ValueSetKind.AllValues)
    @IpsGenerated
    public Money getBeitrag() {
        return beitrag;
    }

    @IpsAttributeSetter("beitrag")
    @IpsGenerated
    public void setBeitrag(Money newValue) {
        this.beitrag = newValue;
    }

Neben dem qualifizierten Klassennamen, muss der Codegenerator also wissen, wie er aus der String-Repräsentation 42.00 EUR eine Instanz der Klasse erzeugt. Methoden, die dies leisten, heißen im JDK meistens valueOf(…​), z. B. bei Integer, Double oder String. Wir fügen so eine Methode zu unserer IsoDate Klasse hinzu. Falls der String nicht geparsed werden kann, wirft unsere Methode analog zu den Methoden im JDK eine Exception.

public static final IsoDate valueOf(String s) {
    if (s==null || s.equals("")) {
        return null;
    }
    String[] tokens = s.split("-");
    int year = Integer.parseInt(tokens[0]);
    int month = Integer.parseInt(tokens[1]);
    int date = Integer.parseInt(tokens[2]);
    return new IsoDate(year, month, date);
}

Das reicht uns bereits um die Java-Klasse IsoDate in Faktor-IPS als Datentyp zu verwenden. In der „.ipsproject“-Datei definieren wir den Datentyp im Element <DatatypeDefinitions> wie folgt:

<Datatypes>
    <UsedPredefinedDatatypes>
        ... unveraendert
    </UsedPredefinedDatatypes>
    <DatatypeDefinitions>
        <Datatype
            id="IsoDate"
            javaClass="enums.IsoDate"
            valueOfMethod="valueOf"/>
    </DatatypeDefinitions>
</Datatypes>

Beim Eingeben muss man darauf achten, das <DatatypeDefinitions> Element richtig zu öffnen und abzuschließen, da vor dem Einfügen des Datentyps die Kurzform <DatatypeDefinitions/> verwendet wird!

Die Bedeutung der Attribute ist wie folgt:

  • id: Die in Faktor-IPS verwendete Identifikation des Datentyps.

  • javaClass: Der qualifizierte Java-Klassenname.

  • valueOfMethod: Der Name der Methode, die eine String-Repräsentation in eine Instanz der Java-Klasse parsed.

Speichern Sie die „.ipsproject“-Datei. Sollte ein Fehler in der Definition vorliegen, wird ein entsprechender Problemmarker angezeigt. Dies ist zum Beispiel der Fall, wenn die Klasse nicht gefunden wird und liegt entweder daran, dass der Java Buildpath nicht richtig definiert ist, oder der Klassenname falsch geschrieben wurde.

Problemview falls die Javaklasse des Datentyps nicht gefunden wird
Figure 1. Problemview falls die Javaklasse des Datentyps nicht gefunden wird

Ist die Definition korrekt, kann der Datentyp sofort im Modell verwendet werden. Legen Sie an der Vertragsklasse ein weiteres Attribut versicherungsBeginn an. In der Liste der Datentypen muss jetzt auch IsoDate erscheinen. Wenn Sie einen ungültigen Defaultwert eingeben, erhalten Sie eine Fehlermeldung während Werte wie 2010-01-01 akzeptiert werden.

Gültigkeitsprüfung eines Wertes gegen den Datentyp im Dialog für Attribute
Figure 2. Beispiel einer Gültigkeitsprüfung eines Wertes gegen den Datentyp im Dialog für Attribute

Nach dem Speichern der Vertragsklasse sollte in der Implementierungsklasse folgender zusätzlicher Sourcecode generiert worden sein:

import enums.IsoDate;

    public static final IsoDate DEFAULT_VALUE_FOR_VERSICHERUNGS_BEGINN = IsoDate.valueOf("2022-01-01");

    private IsoDate versicherungsBeginn = DEFAULT_VALUE_FOR_VERSICHERUNGS_BEGINN;

    @IpsAttribute(name = "versicherungsBeginn", kind = AttributeKind.CHANGEABLE, valueSetKind = ValueSetKind.AllValues)
    @IpsGenerated
    public IsoDate getVersicherungsBeginn() {
        return versicherungsBeginn;
    }

    @IpsAttributeSetter("versicherungsBeginn")
    @IpsGenerated
    public void setVersicherungsBeginn(IsoDate newValue) {
        this.versicherungsBeginn = newValue;
    }

Bevor wir beschreiben wie mit Faktor-IPS Aufzählungen definiert werden können, erläutern wir im Rest des Kapitels noch einige Details bzgl. der Verwendung von Java-Klassen als Datentyp. Dabei handelt es sich aber um weiterführende Informationen, die auch übersprungen werden können.

Faktor-IPS prüft die Gültigkeit der eingegebenen Werte, indem die Java-Klasse IsoDate geladen und per Reflection die valueOf(…​) Methode aufgerufen wird. Aus diesem Grund muss die Klasse im Java-Buildpath des Projektes verfügbar sein. Das ist zwar gegeben, wenn der Sourcecode direkt im Modellprojekt liegt, der Clean-Build von Eclipse bereitet allerdings ein Problem. Beim Clean-Build werden zunächst alle Java Classfiles des Projektes gelöscht, insbesondere natürlich auch das File „IsoDate.class“. Wenn danach der Codegenerator den Sourcecode erzeugt, wird auch der Datentyp IsoDate benötigt. Dieser ist in diesem Augenblick aber nicht valide, da die Java-Klasse („IsoDate.class“) nicht mehr gefunden werden kann. Aus diesem Grund müssen Klassen, die als Datentyp verwendet werden, immer in eigenen Projekten verwaltet werden oder in Jar-Files zur Verfügung gestellt werden.

Zur Prüfung ob ein String einen Wert des Datentyps darstellt, kann zusätzlich eine Methode angegeben werden, die true zurück liefert, wenn der String geparsed werden kann und ansonsten false. Das XML-Attribut zur Angabe des Methodennamen heißt isParsableMethod.

Java-Klassen, die eine Aufzählung definieren, können ebenfalls über den Mechanismus als Datentyp verwendet werden. Hierzu muss das XML-Attribut isEnumType="true" gesetzt werden und die folgenden zusätzlichen Informationen angegeben werden.

XML-Attribut Erläuterung

isSupportingNames

true, falls es eine Methode gibt, die zu einem Wert eine für den Benutzer verständliche Bezeichnung zurückgeben kann. Dies sollte bei Aufzählungen eigentlich immer der Fall sein. Ansonsten false.

getNameMethod

Der Name der - nicht statischen - Methode, die die für den Benutzer verständliche Bezeichnung eines Wertes zurückgibt.

getAllValuesMethod

Der Name der statischen Methode, die alle Werte zurückgeben kann. Die zurückgegebenen Werte sind Instanzen der Klasse, die als Datentyp registriert werden soll und können ein Array oder eine Collection sein.

Aufzählung werden in der UI immer mit allen verfügbaren Werten angezeigt.

Java-Klassen die Value Objects sind aber auch eindeutige Namen haben können mit isSupportingNames="true" in Faktor-IPS nutzerfreundlicher angezeigt werden z.B. für ein Land "Deutschland (DE)".

XML-Attribut Erläuterung

getNameMethod

Der Name der - nicht statischen - Methode, die die für den Benutzer verständliche Bezeichnung eines Wertes zurückgibt.

getAllValuesMethod

Der Name der statischen Methode, die alle Werte zurückgeben kann. Die zurückgegebenen Werte sind Instanzen der Klasse, die als Datentyp registriert werden soll und kann ein Array oder eine Collection sein.

getValueByNameMethod

Der Name der statischen Methode, die den einen Werte passend zu seinem Namen zurückgeben kann. Der zurückgegebene Werte ist eine Instanzen der Klasse, die als Datentyp registriert werden soll.

Für eine Value Object kann wahlweise die Methode getAllValuesMethod oder getValueByNameMethod implementiert werden.

Neben der Definition von Datentypen im eigentlichen Sinne, können an dieser Stelle (konzeptionell vielleicht etwas unsauber) auch Java-Klassen registrieren werden, die nicht als Datentyp (für Attribute) verwendet werden, sondern als Typ für Parameter und Rückgabetypen von Methoden. So könnte man zum Beispiel die Faktor-IPS Klasse MessageList wie folgt zur Verwendung in Methoden zulassen.

<Datatype
    id="MessageList"
    javaClass="org.faktorips.runtime.MessageList"
    valueObject="false"/>