Menu

Seite41B6

Anonymous

4.1.11.6 Models

ModelMain.xpt

ModelMain ist Einstiegspunkt für die Generierung der Edit- und TableModel-Klassen (Datenmodelle für die Tabellendarstellung und das Bearbeitungsformular der Entitäten).

«IMPORT ecore»

«DEFINE modelMain FOR EPackage»

   «EXPAND TableModel::tableModel FOR this»
   «EXPAND EditModel::editModel FOR this»

   «FOREACH this.eClassifiers.typeSelect(EClass) AS entity»
      «EXPAND EntityTableModel::entityTableModel FOR entity»
      «EXPAND EntityEditModel::entityEditModel FOR entity»
   «ENDFOREACH»

«ENDDEFINE»

Die Templates TableModel.xpt und EditModel.xpt beschreiben die Klassen, die die Entitäts-unabhängigen Eigenschaften und Methoden der Edit- bzw. TableModels beschreiben. In ihnen wird einzig der Package-Parameter ausgelesen, um benötigte Klassen aus anderen Paketen zu importieren. Sie werden deshalb hier nicht extra abgebildet.

EntityTableModel.xpt

Allgemein

Glücklicherweise kann die Oberklasse TableModel.java einen Großteil der Aufgaben übernehmen, da diese Entitäts-unspezifisch sind. Für die Entitäts-spezifischen TableModels verbleibt die Aufgabe, die Daten aus dem POJO in String-Werte für die Tabellen-Zellen zu konvertieren. Meist ist diese Umformung unkompliziert. Bei Datumswerten ist allerdings eine Formatierung notwendig.

Template

«IMPORT ecore»
«EXTENSION de::uni_leipzig::dreirad::generator::templates::common::Common»

«DEFINE entityTableModel FOR EClass»
   «FILE getPackageName().getAsPath() + "/client/model/table/" + getClassName() + "TableModel.java"»
      package «getPackageName()».client.model.table;
      import java.util.Set;
      import «getPackageName()».client.helper.Formatter;
      import «getPackageName()».common.ServiceException;
      import «getPackageName()».common.entities.«getClassName()»;

      public class «getClassName()»TableModel extends EntityTableModel

Generat

Beispielhaft ergibt sich

public class CustomerTableModel extends EntityTableModel<Customer> {
    private static final long serialVersionUID = 3259823070881628573L;

    public CustomerTableModel() throws ServiceException {
        super(Customer.class);
    }

    public CustomerTableModel(Set<Customer> customerSet)
        throws ServiceException {
        super(customerSet, Customer.class);
    }

    public Object getValueAt(int row, int col) {
        Customer customer = (Customer) datalist.get(row);

        if (customer == null) {
            return null;
        }

        switch (col) {
        case 0:
            return Formatter.getStringValue(customer.getId());

        case 1:
            return Formatter.getStringValue(customer.getName());

        case 2:
            return Formatter.getFormattedDate(customer.getDateOfBirth());

        case 3:
            return Formatter.getStringValue(customer.getEmailAddress());

        default:
            return null;
        }
    }
}

EntityEditModel.xpt

Allgemein

Während man beim TableModel den Vorteil hatte, dass vieles Entitäts-unspezifisch definiert werden konnte, müssen in den EditModel-Klassen bzw. im Template EntityEditModel.xpt die Entitäts-spezifischen Aspekte wie Datentypen der Attribute und die Relationen beachtet werden.

Templatierung allgemein

«IMPORT ecore»
«EXTENSION de::uni_leipzig::dreirad::generator::templates::common::Common»
«EXTENSION de::uni_leipzig::dreirad::generator::templates::common::TypeMapping»

«DEFINE entityEditModel FOR EClass»
   «FILE getPackageName().getAsPath() + "/client/model/edit/" + getClassName() + "EditModel.java"»
      package «getPackageName()».client.model.edit;
      «EXPAND importDefinition FOR this»

      /**
       * This class sets up  and reads out the form where to edit/create new an object.
       * @author Benjamin Günther
       *
       */
      public class «getClassName()»EditModel extends EntityEditModel

Templatierung der Import-Statements

Bei den Importen müssen gemäß der Übersicht unter Umgang mit den Datentypen die für die Datentypen der Entität passenden Sichtelemente in der Klasse verfügbar gemacht werden. Da jede Entität ein Id-Attribut vom Typ Long besitzt und Long-Werte in einzeiligen Textfeldern dargestellt werden, ist JTextField standardmäßig gesetzt. Kommen in der Entität Boolean- oder Datums-Attribute vor, so ist der zusätzliche Import von JCheckBox bzw. die Imports der Klassen rund um JDateChooser notwendig.

...
«DEFINE importDefinition FOR EClass»
   ...
   import javax.swing.JTextField;
   ...
   import «getPackageName()».common.entities.«getClassName()»;
   «FOREACH this.eSuperTypes.add(this) AS class»
      «EXPAND importDefinitionForAttributes FOR class»
      «EXPAND importDefinitionForRelations FOR class»
   «ENDFOREACH»
   «EXPAND importDefinitionForRelationTabs FOR this»
«ENDDEFINE»

«DEFINE importDefinitionForAttributes FOR EClass»
   «FOREACH eContents.typeSelect(EAttribute) AS attribute»
      «LET attribute.eType.name AS attributeType»
         «IF attribute.eType.name.matches("EBooleanObject")»
            import javax.swing.JCheckBox;
         «ELSEIF attribute.eType.name.matches("EDate")»
            import java.util.Date;
            import java.util.Calendar;
            import com.toedter.calendar.JDateChooser;
            import javax.swing.JButton;
         «ENDIF»
      «ENDLET»
   «ENDFOREACH»
«ENDDEFINE»

«DEFINE importDefinitionForRelations FOR EClass»
   «FOREACH eContents.typeSelect(EReference) AS reference»
      import «getPackageName()».common.entities.«reference.getClassName()»;
      import «getPackageName()».client.model.table.«reference.getClassName()»TableModel;
   «ENDFOREACH»
«ENDDEFINE»

«DEFINE importDefinitionForRelationTabs FOR EClass»
   «IF getAllRelations().exists(relation|relation.lowerBound != 0)»
      import «getPackageName()».client.view.JTab;
      import java.awt.Component;
   «ENDIF»
«ENDDEFINE»
...

Für jede Relation der Entität wird das POJO und das TableModel der verbundenen Entität importiert. Wenn die Entität keine Referenzen auf andere Entitäten besitzt, wird auf den Import von JTab und Component verzichtet.

Auf ähnliche Weise wie die Imports erfolgt die Templatierung der globalen Variablen, die deshalb hier nicht gesondert vorgestellt werden soll.

Templatierung des Konstruktor

Im Konstruktor des jeweiligen EditModels werden die Sichtelemente initialisiert (sie werden aus dem FormPanel der Abeille Forms geholt) und mit den Anfangswerten belegt. Zudem bekommen die Labels (ebenfalls über das FormPanel geholt), die vor den Sichtelementen stehen ihre korrekte Beschriftung, die über die ExternalTerms mit Hilfe des Schlüssel, der aus der Bezeichnung des Attributs im Datenmodell abgeleitet wird, geholt wird. Ähnlich wird beim Setzen der Tooltips und ActionCommands vorgegangen. Eine Besonderheit ergibt sich für JTabbedPane. Die Tabs dieser Pane lassen sich nur über einen Index ansprechen. Um direkten Zugriff auf das der jeweiligen Relation zugeordnete Tab zu haben, wurde eine eigene Klasse JTab eingeführt, in der die TabbedPane sowie der Index, der über den Titel der Tab, der den Namen der Referenz enthält, bestimmt wurde, gesetzt wird.

Beim Setzen des Inhalts der Relationstabellen muss nach der Kardinalität der Referenz differenziert werden. Bei Zu-n-Beziehungen kann sofort vom POJO der Entität dieses EditModels das Set geholt werden, mit dem das TableModel konfiguriert wird. Bei Zu-1-Beziehungen wird vom POJO zunächst das einzelne referenzierte Entitätsobjekt geholt, welches anschließend einem dafür erstellten leerem Set hinzugefügt wird. Diese Set, welches dann nur dieses Objekt enthält, wird dann dem TableModel gesetzt.

...
«DEFINE constructorDefinition FOR EClass»
   public «getClassName()»EditModel(EntityViewManager entityViewManager) throws ServiceException {
      super(Entities.«getInstanceName()», «getClassName()».class);

      «getInstanceName()»IdField = panel.getTextField("«getInstanceName()»IdField");
      «getInstanceName()»IdField.setEditable(false);

      «FOREACH this.eSuperTypes.add(this) AS class»
         «EXPAND constructorDefinitionComponentInitForAttributes FOR class»
      «ENDFOREACH»

      tabbedPane = panel.getTabbedPane("«getInstanceName()»RelationsPane");

      //super.setTabForegroundNormal(tabbedPane.getForeground());

      «FOREACH this.eSuperTypes.add(this) AS class»
         «EXPAND constructorDefinitionComponentInitForRelations FOR class»
      «ENDFOREACH»

      errorLabel = panel.getLabel("statusBar");

      panel.getLabel("«getInstanceName()»IdLabel").setText(
              ExternalTerms.getString("«getInstanceName()».edit.view.«getInstanceName()»Id"));

      «FOREACH this.eSuperTypes.add(this) AS class»
         «EXPAND constructorDefinitionSettingLabelTexts(this) FOR class»
      «ENDFOREACH»

      «EXPAND constructorSettingActionCommandsAndTooltips FOR this»

      «getInstanceName()» = new «getClassName()»();

      «FOREACH this.eSuperTypes.add(this) AS class»
         «EXPAND constructorDefinitionFillingRelationTables(this) FOR class»
      «ENDFOREACH»

      «EXPAND constructorDefinitionTabInitForRelations FOR this»

      this.createEmptyForm();
   }// constructor
«ENDDEFINE»

«DEFINE constructorDefinitionComponentInitForAttributes FOR EClass»
   «FOREACH this.eContents.typeSelect(EAttribute) AS attribute»
      «LET attribute.eType.name AS attributeType»
         «IF attributeType.matches("EBooleanObject")»
            «attribute.getAttributeName()»Box = panel.getCheckBox("«attribute.getAttributeName()»Box");
         «ELSEIF attributeType.matches("EDate")»
            «attribute.getAttributeName()»Button = (JButton) panel.getButton(
                    "«attribute.getAttributeName()»CalendarButton");
         «ELSE»
            «attribute.getAttributeName()»Field = panel.getTextField("«attribute.getAttributeName()»Field");
         «ENDIF»
      «ENDLET»
   «ENDFOREACH»
«ENDDEFINE»

«DEFINE constructorDefinitionComponentInitForRelations FOR EClass»
   «FOREACH eContents.typeSelect(EReference) AS reference»
      «reference.getReferenceInstanceName()»Table = panel.getTable(
              "«reference.getReferenceInstanceName()»RelationTable");
   «ENDFOREACH»
«ENDDEFINE»

«DEFINE constructorDefinitionTabInitForRelations FOR EClass»
   «IF getAllRelations().exists(relation|relation.lowerBound != 0)»
      Component [] components = tabbedPane.getComponents();
      String title;
      for(int i=0; i<TableModel> «reference.getReferenceInstanceName()»TableSorter =
              TableSortHelper.getRowSorter(«reference.getReferenceInstanceName()»TableModel);
      «reference.getInstanceName()»Table.setRowSorter(«reference.getReferenceInstanceName()»TableSorter);*/
      ViewHelper.createTableRowDeleteButton(«reference.getReferenceInstanceName()»Table);
      ViewHelper.manageCursor(tabbedPane, «reference.getReferenceInstanceName()»Table,
              «reference.getReferenceInstanceName()»TableModel, entityViewManager);
   «ENDFOREACH»
«ENDDEFINE»
...

Templatierung der Befüllung des Formulars

Das Initialisieren des leeren Formulars ist ähnlich dem Initialiseren des Formulars mit den Werten eines Entitätsobjekts, darum soll nur letzteres hier vorgestellt werden.

...
«DEFINE fillFormDefinition FOR EClass»
   /**
    * All the data of the chosen object is set into the fields fo the form.
    * @param address The chosen object to show and edit.
    */
   private void set«getClassName()»InForm(«getClassName()» «getInstanceName()») {
      this.createEmptyForm();

      «getInstanceName()»IdField.setText(Formatter.getStringValue((«getInstanceName()».getId())));
      «FOREACH this.eSuperTypes.add(this) AS class»
         «EXPAND fillFormDefinitionForAttributes(this) FOR class»
      «ENDFOREACH»

      «FOREACH this.eSuperTypes.add(this) AS class»
         «EXPAND fillFormDefinitionForRelations(this) FOR class»
      «ENDFOREACH»

      this.«getInstanceName()» = «getInstanceName()»;
   }// set«getClassName()»InForm
«ENDDEFINE»

«DEFINE fillFormDefinitionForAttributes(EClass initialClass) FOR EClass»
   «FOREACH this.eContents.typeSelect(EAttribute) AS attribute»
      «LET attribute.eType.name AS attributeType»
         «IF attributeType.matches("EBooleanObject")»
            «attribute.getAttributeName()»Box.setSelected(
                    «initialClass.getInstanceName()».«attribute.getGetMethodName()»());
         «ELSEIF attributeType.matches("EDate")»
            Date «attribute.getAttributeName()» =
                    «initialClass.getInstanceName()».«attribute.getGetMethodName()»();
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(«attribute.getAttributeName()»);
            «attribute.getAttributeName()»Button.removeAll();
            «attribute.getAttributeName()»Chooser =
                    new JDateChooser(«initialClass.getInstanceName()».
                    «attribute.getGetMethodName()»());
            «attribute.getAttributeName()»Chooser.setEnabled(true);
            «attribute.getAttributeName()»Chooser.setVisible(true);
            «attribute.getAttributeName()»Chooser.setDateFormatString("dd.MM.yyyy");
            «attribute.getAttributeName()»Button.add(«attribute.getAttributeName()»Chooser);
         «ELSE»
            «attribute.getAttributeName()»Field.setText(
                    Formatter.getStringValue(«initialClass.getInstanceName()».
                    «attribute.getGetMethodName()»()));
         «ENDIF»
      «ENDLET»
   «ENDFOREACH»
«ENDDEFINE»

«DEFINE fillFormDefinitionForRelations(EClass initialClass) FOR EClass»
   «FOREACH this.eContents.typeSelect(EReference) AS reference»
      «IF reference.upperBound == -1»
         Set

Beim Setzen der Attributwerte genügt bei Zahlen- und String-Attributen, den aus dem POJO ausgelesenen Wert und zuvor als String formatierten Wert über setText in das JTextField zu setzen. Noch einfacher ist es für Boolean: der True- oder False-Wert muss nur der setSelected-Methode der JCheckBox übergeben werden. Etwas umfangreicher aber an sich nicht komplizierter stellt sich der Umgang mit Datums-Attributen dar. Dem JDateChooser muss nur der Date-Wert aus dem POJO übergeben werden. Zudem muss ihm das Datums-Format gesetzt werden. Der Button, über den das Datum ausgewählt werden kann, wird aktiviert und bekommt das JDateChooser-Objekt geadded.
Beim Füllen der Relationstabellen wird genauso vorgegangen wie beim Initialisieren der Tables im Konstruktor, nur mit dem Unterschied, dass den TableModels nun gefüllte Sets übergeben werden.

Templatierung des Auslesens eines Formulars

Beim Auslesen der Daten aus dem Formular müssen Validatoren und Deformatierer eingesetzt werden, die wiederum von den Datentypen der Attribut der Entität abhängen, entsprechend so templatiert werden müssen. So wird einem Datumswert Validator.checkDate(date) aufgerufen. Beim Auslesen von Werten aus Textfeldern wird bei Attributen, die im Datenmodell als required konfiguriert wurden, zusätzlich geprüft, ob der ausgelesene Wert nicht leer ist (String-Länge > 0). Bei Attributen im Datenmodell, für die required auf false gestellt wurde, wird der ausgelesene Wert zunächst ungeprüft übernommen. da auch Zahlen-Attribute in Textfeldern angezeigt werden, muss bei deren Zurücklesen in das POJO geprüft werden, ob wirklich im Textfeld ein Zahl steht. Darum wird vor dem Setzen des Wertes dieser zunächst den dafür vorgesehenen Validator übergeben, der dabei deformatiert wird (z.B. 29,56 zu 29.56). Beim Auslesen der Relationen wird entsprechend der Kardinalitätsrestriktionen im Datenmodell geprüft, ob bei 1-zu-1-Beziehung wirklich genau ein Objekt gesetzt wurde (Aufruf von Validator.checkRelationshipExactCardinality). Bei 1-zu-n-Beziehungen prüft die dafür vorgesehene Validator-Methode, ob mindestens ein Objekt sich im Set der verbundenen Entitätsobjekte befindet (der Aufruf von Validator.checkRelationshipMinimumCardinality wird dafür generiert).

...
«DEFINE readOutFormDefinition FOR EClass»

   /**
    * Gets the data out of the fields, validates it and sets it at the object.
    * @param «getInstanceName()» The object to set the new values at.
    */
   private void get«getClassName()»OutOfForm(«getClassName()» «getInstanceName()») throws ServiceException {
      get«getClassName()»OutOfForm(«getInstanceName()», new HashSet<Entities>());
   }

   /**
    * Gets the data out of the fields, validates it (without the excluded relations) and sets it at the object.
    * @param «getInstanceName()» The object to set the new values at.
    */
   private void get«getClassName()»OutOfForm(«getClassName()» «getInstanceName()»,
           Set<Entities> relationsToExclude) throws ServiceException {
      super.resetComponentState(Validator.getErrorComponents());
      Validator.getErrorComponents().clear();
      errorLabel.setText("");

      try {
         «FOREACH this.eSuperTypes.add(this) AS class»
            «EXPAND readOutFormDefinitionForAttributes(this) FOR class»
         «ENDFOREACH»

         checkRelations(«getInstanceName()», relationsToExclude);

         this.«getInstanceName()» = «getInstanceName()»;

      } catch(ValidationException validationException) {
         super.setComponentErrorState(Validator.getErrorComponents());
         errorLabel.setIcon(errorIcon);
         errorLabel.setText(validationException.getMessage());
         throw new ServiceException("");
      }// try-catch
   } //get«getClassName()»OutOfForm
«ENDDEFINE»

«DEFINE readOutFormDefinitionForAttributes(EClass initialClass) FOR EClass»
   «FOREACH this.eContents.typeSelect(EAttribute) AS attribute»
      «LET attribute.eType.name AS attributeType»
         «IF attributeType.matches("EBooleanObject")»
            Boolean «attribute.getAttributeName()» = «attribute.getAttributeName()»Box.isSelected();
         «ELSEIF attributeType.matches("EDate")»
            Date «attribute.getAttributeName()» = «attribute.getAttributeName()»Chooser.getDate();
            Validator.checkDate(«attribute.getAttributeName()»Button, «attribute.getAttributeName()»);
         «ELSE»
            String «attribute.getAttributeName()»String =
                    «attribute.getAttributeName()»Field.getText().trim();
            «IF attribute.required»
               Validator.checkLength(«attribute.getAttributeName()»Field,
                       "«attribute.getAttributeName()»", «attribute.getAttributeName()»String);
            «ENDIF»
            «IF ePackage.getAllNumberDataTypeNames().contains(attributeType)»
               «attribute.getJavaType()» «attribute.getAttributeName()» =
                       Validator.get«attribute.getJavaType().getNameWithOutPackage()»Value(
                       «attribute.getAttributeName()»Field, "«attribute.getAttributeName()»",
                       «attribute.getAttributeName()»String);
            «ELSE»
               String «attribute.getAttributeName()» = «attribute.getAttributeName()»String;
            «ENDIF»
         «ENDIF»
         «initialClass.getInstanceName()».«attribute.getSetMethodName()»(«attribute.getAttributeName()»);
      «ENDLET»
   «ENDFOREACH»
«ENDDEFINE»

«DEFINE readOutFormDefinitionForRelations(EClass initialClass) FOR EClass»
   private void checkRelations(«getClassName()» «getInstanceName()»,
           Set<Entities> relationsToExclude) throws ValidationException {
      «FOREACH this.eSuperTypes.add(this).getAllRelations() AS reference»
         if(!relationsToExclude.contains(Entities.«reference.getInstanceName()»)) {
            Set <Entities> excludedRelations = new HashSet<Entities>();
         excludedRelations.add(Entities.«reference.getInstanceName()»);
         get«getClassName()»OutOfForm(new «getClassName()»(), excludedRelations);
         return «getInstanceName()»;
      }// getNewEntityWithout«reference.getClassName()»
   «ENDFOREACH»
«ENDDEFINE»
...

In den EditModels wurde die Möglichkeit geschaffen, an die Werte des Formulars zu kommen, ohne das eine oder mehrere Referenzen auf Erfüllen der Kardinalitäten geprüft wird (die betreffenden Referenzen müssen dafür der excludedRelations hinzugefügt werden). Diese Variante des Auslesens ist für den Fall gedacht, bei dem aus dem Anlege-Formular der einen Entität ein verbundenes Entitätsobjekt angelegt werden soll. Für das Beispiel Artikel-ArtikelTyp ergibt sich folgendes Szenario: es wird das Anlege-Formular für ArtikelTyp aufgerufen. Aus diesem wird das Anlege-Formular für einen neuen Artikel gestartet, der diesem ArtikelTyp zugeordnet werden soll. Beim Drücken auf den Speichern-Button im Artikel-Formular werden die Werte aus dem Formular ausgelesen und dabei auf Korrektheit geprüft. Dabei würde normalerweise bemängelt werden, dass noch kein ArtikelTyp gesetzt wurde. Dieser soll aber erst noch gespeichert werden, darum wird hier die Validierung der Relation zum ArtikelTyp im AnlegeFormular des Artikels umgangen. Beim Speichern des ArtikelTyps wird das ArtikelTyp-Objekt nebst dem Artikel-Objekt (das noch keine Referenz auf den Artikeltypen hat) an den Server übertragen. Auf Serverseite wird dann auch die Referenz von Artikel auf ArtikelTyp gesetzt, bevor der ArtikelTyp gespeichert wird.


weiter zu 4.2.5.7 Validierung
zurück zu 4.2.5.5 Controller
zurück zu 4.2.5 Templatierung der Präsentationsschicht
zurück zu 4.2 Implementierung des Generators
zurück zu 4 Implementierung
zurück zu [FrontPage]


Related

Documentation: FrontPage
Documentation: Seite000
Documentation: Seite400
Documentation: Seite4192
Documentation: Seite41B
Documentation: Seite41B5
Documentation: Seite41B7
Documentation: Seite420