Επισκόπηση του EMF (Eclipse Modeling Framework)

Τελευταία ενημέρωση: 16 Ιουνίου 2005 (Ενημέρωση λειτουργιών διευκόλυνσης πρόσβασης)

Αυτό το έγγραφο παρουσιάζει μια βασική επισκόπηση του EMF και των σχετικών μοτίβων δημιουργίας κώδικα. Για μια πιο λεπτομερή περιγραφή όλων των λειτουργιών του EMF, ανατρέξτε στο έγγραφο EMF: Eclipse Modeling Framework, Second Edition (Addison-Wesley Professional, 2008) ή στο Javadoc για τις κλάσεις του πλαισίου.

Περιεχόμενα

Εισαγωγή
Ορισμός μοντέλου EMF
Δημιουργία υλοποίησης Java
Χρήση των δημιουργημένων κλάσεων EMF
Ειδικά θέματα

Εισαγωγή

Το EMF παρέχει ένα πλαίσιο Java και ένα βοηθητικό πρόγραμμα δημιουργίας κώδικα για την ανάπτυξη εργαλείων και άλλων εφαρμογών με βάση δομημένα μοντέλα. Αν ασχολείστε συστηματικά με την αντικειμενοστραφή μοντελοποίηση, το EMF μπορεί να σας βοηθήσει να μετατρέπετε γρήγορα τα μοντέλα σας σε αποτελεσματικό, σωστό και εύκολα προσαρμόσιμο κώδικα Java. Αν πάλι δεν είστε εξοικειωμένος με τα μοντέλα και τη μοντελοποίηση, το EMF αποτελεί μια εύχρηστη και οικονομική λύση για να ξεκινήσετε.

Ας δούμε όμως τι εννοούμε με τον όρο "μοντέλο". Όταν μιλάμε για μοντελοποίηση, συνήθως σκεφτόμαστε πράγματα όπως τα διαγράμματα κλάσεων, τα διαγράμματα συνεργασίας, τα διαγράμματα κατάστασης κ.ο.κ. Η UML (Unified Modeling Language) ορίζει μια τυποποιημένη σημειογραφία για αυτά τα είδη διαγραμμάτων. Χρησιμοποιώντας ένα συνδυασμό διαγραμμάτων UML, μπορούμε να ορίσουμε ένα ολοκληρωμένο μοντέλο μιας εφαρμογής. Αυτό το μοντέλο μπορεί να χρησιμοποιηθεί αποκλειστικά ως τεκμηρίωση ή, εφόσον υπάρχουν τα κατάλληλα εργαλεία, μπορεί να αποτελέσει τη βάση για τη δημιουργία ενός μέρους ή, σε απλές περιπτώσεις, ολόκληρης της εφαρμογής.

Με δεδομένο ότι αυτό το είδος μοντελοποίησης απαιτεί συνήθως ακριβά εργαλεία αντικειμενοστραφούς ανάλυσης και σχεδιασμού (Object Oriented Analysis and Design - OOA/D), εύλογα αναρωτιέται κανείς γιατί το EMF αποτελεί μια οικονομική λύση. Ο λόγος είναι ότι ένα μοντέλο EMF απαιτεί μόνο ένα μικρό υποσύνολο των στοιχείων που μπορούν να μοντελοποιηθούν στη UML, ειδικότερα απλούς ορισμούς κλάσεων με τα γνωρίσματα και τις σχέσεις τους, στοιχεία για τα οποία δεν απαιτείται η χρήση ενός σύνθετου εργαλείου γραφικού περιβάλλοντος μοντελοποίησης.

Καθώς το EMF χρησιμοποιεί το πρότυπο XMI (XML Metadata Interchange) ως κανονική μορφή για τους ορισμούς μοντέλων[1] , μπορείτε να μεταφέρετε τα μοντέλα σας σε αυτή τη μορφή με διάφορες μεθόδους:

Η πρώτη μέθοδος αποτελεί την πιο άμεση προσέγγιση, αλλά ενδείκνυται μόνο για όσους έχουν πολύ καλή γνώση της XML. Η δεύτερη μέθοδος είναι η ιδανική επιλογή για όσους ήδη χρησιμοποιούν επαγγελματικά εργαλεία μοντελοποίησης. Η τρίτη μέθοδος απευθύνεται στους προγραμματιστές Java. Πρόκειται για μια οικονομική προσέγγιση που επιτρέπει στους προγραμματιστές να εκμεταλλεύονται τα πλεονεκτήματα του EMF και της γεννήτριας κώδικα του EMF χρησιμοποιώντας ένα βασικό περιβάλλον προγραμματισμού Java (π.χ. τα εργαλεία ανάπτυξης Java του Eclipse). Η τελευταία μέθοδος είναι κατάλληλη για τη δημιουργία εφαρμογών με δυνατότητα εγγραφής ή ανάγνωσης μιας συγκεκριμένης μορφής αρχείων XML.

Αφού ορίσετε ένα μοντέλο EMF, η γεννήτρια του EMF μπορεί να δημιουργήσει το αντίστοιχο σύνολο κλάσεων υλοποίησης Java. Στη συνέχεια, μπορείτε να τροποποιήσετε αυτές τις κλάσεις για να προσθέσετε μεθόδους και μεταβλητές χρήσης. Αν χρειάζεται, μπορείτε να επαναδημιουργήσετε τις κλάσεις από το μοντέλο, χωρίς να χάσετε τις προσθήκες που έχετε κάνει. Αν προσθέσετε κώδικα που εξαρτάται από κάποιο στοιχείο που έχει αλλάξει στο μοντέλο, θα πρέπει να ενημερώσετε κατάλληλα το μοντέλο. Διαφορετικά, ο κώδικάς σας δεν θα επηρεαστεί από τις αλλαγές στο μοντέλο και από τη διαδικασία επαναδημιουργίας.

Εκτός από τη βελτίωση της παραγωγικότητάς σας, όταν χρησιμοποιείτε το EMF για τη δημιουργία των εφαρμογών σας, απολαμβάνετε μια σειρά από ευκολίες και πρόσθετες δυνατότητες που παρέχει το EMF (π.χ. δυνατότητα λήψης ειδοποιήσεων σε περίπτωση αλλαγών στα μοντέλα, υποστήριξη μονιμοποίησης, πλαίσιο υποδομής για την επικύρωση των μοντέλων, API για το γενικό χειρισμό των αντικειμένων EMF). Το σημαντικότερο όμως πλεονέκτημα του EMF είναι ότι θέτει τις βάσεις για τη διαλειτουργικότητα των εργαλείων και των εφαρμογών που βασίζονται στο EMF.

Το EMF αποτελείται από δύο βασικά πλαίσια: το πλαίσιο πυρήνα και το πλαίσιο EMF.Edit. Το πλαίσιο πυρήνα παρέχει τη βασική λειτουργική υποστήριξη υπηρεσιών γεννήτριας και περιβάλλοντος εκτέλεσης για τη δημιουργία κλάσεων υλοποίησης Java για ένα μοντέλο. Το πλαίσιο EMF.Edit βασίζεται στο πλαίσιο πυρήνα και το επεκτείνει προσθέτοντας υποστήριξη για τη δημιουργία κλάσεων προσαρμογής που επιτρέπουν την προβολή και την τροποποίηση των μοντέλων με χρήση εντολών. Επιπλέον, παρέχει μια βασική λειτουργία επεξεργασίας μοντέλων. Οι ακόλουθες ενότητες αυτού του εγγράφου περιγράφουν τις βασικές λειτουργίες του πλαισίου πυρήνα του EMF. Το πλαίσιο EMF.Edit περιγράφεται σε ένα ξεχωριστό έγγραφο με τίτλο Επισκόπηση του EMF.Edit. Για οδηγίες σχετικά με την εκτέλεση του EMF και της γεννήτριας EMF.Edit, ανατρέξτε στο πρόγραμμα εκμάθησης Δημιουργία μοντέλου EMF.

Σχέση EMF και OMG MOF

Αν είστε εξοικειωμένοι με το OMG (Object Management Group) MOF (Meta Object Facility), θα αναρωτιέστε ποια είναι η σχέση του με το EMF. Το EMF ξεκίνησε ως μια υλοποίηση του MOF, αλλά εξελίχθηκε χάρη στην εμπειρία που αποκτήσαμε χρησιμοποιώντας το για την υλοποίηση ενός σημαντικού αριθμού εργαλείων. Το EMF μπορεί να θεωρηθεί ως μια ιδιαίτερα αποτελεσματική υλοποίηση ενός υποσυνόλου βασικών λειτουργιών του MOF API. Ωστόσο, για την αποφυγή συγχύσεων, το σχετικό με τον πυρήνα MOF μεταμοντέλο στο EMF ονομάζεται Ecore.

Στην τρέχουσα πρόταση για το MOF 2.0, υπάρχει ένα παρόμοιο υποσύνολο του μοντέλου MOF με την ονομασία EMOF (Essential MOF). Οι διαφορές μεταξύ του Ecore και του EMOF είναι μικρές και εστιάζονται κυρίως σε θέματα ονοματοθεσίας. Το EMF υποστηρίζει πλήρως την ανάγνωση και την εγγραφή σειριοποιήσεων του EMOF.

Ορισμός μοντέλου EMF

Για την καλύτερη περιγραφή του EMF, θα χρησιμοποιήσουμε ένα απλό παράδειγμα. Ας υποθέσουμε λοιπόν ότι έχουμε το ακόλουθο μοντέλο μίας κλάσης:

Μοντέλο μίας κλάσης Book με τίτλο είδους String και σελίδες είδους int

Το μοντέλο παρουσιάζει μία κλάση με όνομα Book και δύο γνωρίσματα, ένα γνώρισμα τίτλου (title) του είδους String και ένα γνώρισμα σελίδων (pages) του είδους int.

Ο ορισμός του μοντέλου μπορεί να παρασχεθεί στη γεννήτρια κώδικα του EMF με διάφορους τρόπους.

UML

Αν έχετε ένα εργαλείο μοντελοποίησης που συνεργάζεται με το EMF[2] , μπορείτε απλά να σύρετε το διάγραμμα κλάσεων όπως φαίνεται παραπάνω.

XMI

Εναλλακτικά, μπορούμε να περιγράψουμε το μοντέλο απευθείας σε ένα έγγραφο XMI. Η περιγραφή του μοντέλου στο παράδειγμά μας θα είναι ως εξής:

  <ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
      name="library "nsURI="http:///library.ecore" nsPrefix="library">
    <eClassifiers xsi:type="ecore:EClass" name="Book">
      <eStructuralFeatures xsi:type="ecore:EAttribute" name="title"
          eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
      <eStructuralFeatures xsi:type="ecore:EAttribute" name="pages"
          eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
    </eClassifiers>
  </ecore:EPackage>

Το έγγραφο XMI περιέχει όλες τις πληροφορίες του διαγράμματος κλάσεων σε πιο αναλυτική μορφή. Για κάθε κλάση και γνώρισμα σε ένα διάγραμμα υπάρχει ένας αντίστοιχος ορισμός κλάσης ή διαγράμματος στο έγγραφο XMI.

Java με σημειώσεις

Αν δεν διαθέτετε ένα εργαλείο γραφικού περιβάλλοντος μοντελοποίησης και δεν θέλετε να καταχωρήσετε την περιγραφή του μοντέλου σε ένα έγγραφο XMI, υπάρχει μια τρίτη εναλλακτική λύση. Με δεδομένο ότι η γεννήτρια του EMF είναι μια γεννήτρια συγχώνευσης κώδικα, μπορείτε να προσθέσετε σημειώσεις με τις πληροφορίες του μοντέλου σε διεπαφές Java που θα προωθηθούν στη γεννήτρια κώδικα. Η γεννήτρια θα χρησιμοποιήσει τις διεπαφές ως μεταδεδομένα δημιουργίας και θα συγχωνεύσει τον κώδικα που θα δημιουργηθεί με την υπόλοιπη υλοποίηση.

Στο παράδειγμά μας, μπορούμε να ορίσουμε την κλάση Book στην Java ως εξής:

  /**
   * @model
   */
  public interface Book
  {
    /**
     * @model
     */
    String getTitle();

    /**
     * @model
     */
    int getPages();
  }

Με αυτό τον τρόπο, παρέχουμε όλες τις πληροφορίες του μοντέλου σε μορφή διεπαφών Java με τυπικές μεθόδους get[3]  για τον προσδιορισμό των γνωρισμάτων και των παραπομπών. Το προσδιοριστικό @model χρησιμοποιείται από τη γεννήτρια κώδικα για την αναγνώριση των διεπαφών, και των τμημάτων αυτών των διεπαφών, που αντιστοιχούν στα στοιχεία του μοντέλου και απαιτούν τη δημιουργία κώδικα.

Στο απλό παράδειγμά μας, όλες οι πληροφορίες του μοντέλου είναι διαθέσιμες μέσω της αυτοανάλυσης αυτής της διεπαφής Java, οπότε δεν απαιτούνται πρόσθετες πληροφορίες για το μοντέλο. Σε γενικές γραμμές όμως, το προσδιοριστικό @model μπορεί να συνοδεύεται από πρόσθετες πληροφορίες για τα στοιχεία του μοντέλου. Για παράδειγμα, αν θέλαμε το προσδιοριστικό σελίδων (pages) να είναι μόνο για ανάγνωση, θα έπρεπε να προσθέσουμε τις ακόλουθες πληροφορίες στη σημείωση:

  /**
   * @model changeable="false"
   */
  int getPages();

Επειδή απαιτείται ο ορισμός μόνο των πληροφοριών που διαφέρουν από τις προεπιλεγμένες πληροφορίες, οι σημειώσεις μπορούν να διατηρούνται σύντομες και απλές.

Σχήμα XML

Μερικές φορές, μπορεί να χρειάζεται να περιγράψετε ένα μοντέλο με ένα σχήμα που θα ορίζει μια συγκεκριμένη σειριοποίηση χρήσης. Η περιγραφή του μοντέλου με αυτό τον τρόπο ενδείκνυται, για παράδειγμα, όταν θέλετε να δημιουργήσετε μια εφαρμογή που θα χρησιμοποιεί XML για την ενοποίησή της με μια υπάρχουσα εφαρμογή ή για τη συμμόρφωσή της με τις προδιαγραφές ενός προτύπου. Για να περιγράψουμε το μοντέλο του παραδείγματος μας με αυτό τον τρόπο, θα ορίσουμε ένα σχήμα ως εξής:

  <xsd:schema targetNamespace="http:///library.ecore"
      xmlns="http:///library.ecore" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:complexType name="Book">
      <xsd:sequence>
        <xsd:element name="title" type="xsd:string"/>
        <xsd:element name="pages" type="xsd:integer"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:schema>

Αυτός ο τρόπος περιγραφής ενός μοντέλου αποτελεί μια εναλλακτική προσέγγιση που διαφέρει από τις υπόλοιπες τρεις μεθόδους που είδαμε παραπάνω. Η βασική διαφορά έγκειται στο ότι το EMF εφαρμόζει ορισμένους περιορισμούς στη χρησιμοποιούμενη σειριοποίηση για να διασφαλίσει τη συμμόρφωση με το σχήμα. Ως αποτέλεσμα, το μοντέλο που δημιουργείται για ένα σχήμα έχει κάποιες διαφορές από το μοντέλο που ορίζεται με μία από τις άλλες μεθόδους. Οι διαφορές αυτές δεν παρουσιάζονται σε αυτό το έγγραφο.

Στη συνέχεια αυτού του εγγράφου, θα παρουσιάσουμε ορισμένες έννοιες μοντελοποίησης χρησιμοποιώντας ως παραδείγματα διαγράμματα UML. Επιλέξαμε να χρησιμοποιήσουμε διαγράμματα UML γιατί είναι σαφή και συνοπτικά. Για όλες τις έννοιες, θα μπορούσαν εναλλακτικά να χρησιμοποιηθούν παραδείγματα επισημείωσης διεπαφών Java ή απευθείας δημιουργίας εγγράφων XMI. Επιπλέον, θα μπορούσαν να χρησιμοποιηθούν σχήματα XML για την παρουσίαση των περισσοτέρων εννοιών. Ανεξάρτητα από τη μέθοδο παροχής των πληροφοριών, ο δημιουργούμενος κώδικάς από το EMF θα είναι ίδιος.

Δημιουργία υλοποίησης Java

Για κάθε κλάση στο μοντέλο, δημιουργούνται μια διεπαφή Java και η αντίστοιχη κλάση υλοποίησης. Στο παράδειγμά μας, θα δημιουργηθεί η ακόλουθη διεπαφή για την κλάση Book:

  public interface Book extends EObject
  {
    String getTitle();
    void setTitle(String value);

    int getPages();
    void setPages(int value);
  }

Κάθε δημιουργούμενη διεπαφή περιέχει μεθόδους λήψης (get) και ορισμού (set) για κάθε γνώρισμα και κάθε παραπομπή της αντίστοιχης κλάσης μοντέλου.

Η διεπαφή Book επεκτείνει τη διεπαφή EObject. Η διεπαφή EObject στο EMF είναι παρόμοια με τη διεπαφή java.lang.Object της Java, δηλαδή αποτελεί τη βάση κάθε κλάσης στο EMF. Η διεπαφή EObject με την αντίστοιχη κλάση υλοποίησης EObjectImpl παρέχουν μια βασική κλάση που επιτρέπει τη συμμετοχή της κλάσης Book στα πλαίσια σημειογραφίας και μονιμοποίησης του EMF. Πριν εξετάσουμε τον τρόπο λειτουργίας της διεπαφής EObject, ας δούμε μερικές ακόμα πληροφορίες σχετικά με τη διαδικασία δημιουργίας στο EMF.

Κάθε δημιουργούμενη κλάση υλοποίησης περιλαμβάνει τις μεθόδους λήψης (get) και ορισμού (set) που ορίζονται στην αντίστοιχη διεπαφή. Επιπλέον, περιλαμβάνει ορισμένες άλλες μεθόδους που απαιτούνται από το πλαίσιο EMF.

Η κλάση BookImpl θα περιλαμβάνει μεταξύ άλλων υλοποιήσεις των μεθόδων πρόσβασης για τα γνωρίσματα τίτλου (title) και σελίδων (pages). Για παράδειγμα, το γνώρισμα σελίδων θα έχει την ακόλουθη υλοποίηση:

  public class BookImpl extends EObjectImpl implements Book
  {
    ...
    protected static final int PAGES_EDEFAULT = 0;
    protected int pages = PAGES_EDEFAULT;

    public int getPages()
    {
      return pages;
    }

    public void setPages(int newPages)
    {
      int oldPages = pages;
      pages = newPages;
      if (eNotificationRequired())
        eNotify(new ENotificationImpl(this, Notification.SET, ..., oldPages, pages));
    }

    ...
  }

Η δημιουργούμενη μέθοδος get έχει βέλτιστη απόδοση. Επιστρέφει απλά μια μεταβλητή χρήσης για το γνώρισμα.

Η μέθοδος set, παρόλο που είναι λίγο πιο πολύπλοκη, είναι εξίσου αποδοτική. Εκτός από τον ορισμό της μεταβλητής χρήσης για το γνώρισμα pages, η μέθοδος set πρέπει επίσης να αποστείλει μια ειδοποίηση αλλαγής σε οποιονδήποτε παρατηρητή εκτελεί ακρόαση στο αντικείμενο. Η αποστολή της ειδοποίησης γίνεται με την κλήση της μεθόδου eNotify(). Επειδή μπορεί να μην υπάρχουν πάντα παρατηρητές (π.χ. στην περίπτωση μιας εφαρμογής μαζικής επεξεργασίας), η κατασκευή του αντικειμένου ειδοποίησης (ENotificationImpl) και η κλήση της μεθόδου eNotify() προστατεύονται με μια κλήση στη μέθοδο eNotificationRequired(). Η προεπιλεγμένη υλοποίηση της μεθόδου eNotificationRequired() ελέγχει απλά αν υπάρχουν παρατηρητές (προσαρμογείς) προσαρτημένοι στο αντικείμενο. Ως εκ τούτου, όταν τα αντικείμενα EMF χρησιμοποιούνται χωρίς παρατηρητές, η κλήση στη μέθοδο eNotificationRequired() επιστρέφει απλά ένα δείκτη null ο οποίος ενσωματώνεται κατά τη χρήση ενός μεταγλωττιστή JIT.

Τα δημιουργούμενα μοτίβα μεθόδου πρόσβασης για άλλα είδη γνωρισμάτων, όπως για παράδειγμα το γνώρισμα title του είδους String, έχουν μερικές μικρές διαφορές, αλλά είναι κατά βάση ίδια με αυτά για το γνώρισμα pages[4] .

Οι δημιουργούμενες μέθοδοι πρόσβασης για τις παραπομπές, ειδικά τις αμφίδρομες, είναι λίγο πιο πολύπλοκες και αποτελούν ένα καλό παράδειγμα για να αρχίσει κανείς να καταλαβαίνει την αξία της γεννήτριας του EMF.

Μονόδρομες παραπομπές

Ας επεκτείνουμε το μοντέλο του παραδείγματός μας με μια κλάση Writer που είναι συσχετισμένη με την κλάση Book:

Μονόδρομη παραπομπή: Ένα βιβλίο (κλάση Book) με έναν μεμονωμένο συγγραφέα (κλάση Writer) με όνομα String.

Σε αυτό το παράδειγμα, η συσχέτιση ανάμεσα σε ένα βιβλίο (Book) και το συγγραφέα (Writer) του αποτελεί μια μονόδρομη παραπομπή. Το όνομα της παραπομπής (ρόλος) που χρησιμοποιείται για την πρόσβαση στην κλάση Writer από μια κλάση Book είναι author.

Η εκτέλεση αυτού του μοντέλου μέσω της γεννήτριας του EMF, εκτός από τη νέα διεπαφή Writer και την κλάση υλοποίησης WriterImpl, θα δημιουργήσει πρόσθετες μεθόδους get και set στη διεπαφή Book:

  Writer getAuthor();
  void setAuthor(Writer value);

Καθώς η παραπομπή για το συγγραφέα (Author) είναι μονόδρομη, η υλοποίηση της μεθόδου setAuthor() δείχνει ως μια απλή μέθοδος ορισμού δεδομένων όπως η μέθοδος setPages():

  public void setAuthor(Writer newAuthor)
  {
    Writer oldAuthor = author;
    author = newAuthor;
    if(eNotificationRequired())
      eNotify(new ENotificationImpl(this, ...));
  }

Η μόνη διαφορά είναι ότι εδώ ορίζουμε ένα δείκτη αντικειμένου και όχι ένα απλό πεδίο δεδομένων.

Ωστόσο, επειδή πρόκειται για μια παραπομπή αντικειμένου, η μέθοδος getAuthor() είναι λίγο πιο σύνθετη. Αυτό συμβαίνει γιατί η μέθοδος get για ορισμένα είδη παραπομπών, συμπεριλαμβανομένου του είδους της παραπομπής για το συγγραφέα, πρέπει να είναι σε θέση να χειριστεί την περίπτωση όπου το παραπεμπόμενο αντικείμενο (στο παράδειγμά μας, Writer) είναι μονιμοποιημένο σε διαφορετικό πόρο (έγγραφο) από το αντικείμενο προέλευσης (στο παράδειγμά μας, Book). Καθώς το πλαίσιο μονιμοποίησης του EMF χρησιμοποιεί ένα "τεμπέλικο" σχήμα φόρτωσης, ένας δείκτης αντικειμένου (στο παράδειγμά μας, author) μπορεί ορισμένες φορές να είναι ένας αντιπρόσωπος (proxy) του αντικειμένου και όχι το ίδιο αντικείμενο στο οποίο γίνεται παραπομπή[5] . Ως αποτέλεσμα, η μέθοδος getAuthor() θα έχει την ακόλουθη μορφή:

  public Writer getAuthor()
  {
    if (author != null && author.eIsProxy())
    {
      Writer oldAuthor = author;
      author = (Writer)eResolveProxy((InternalEObject)author);
      if (author != oldAuthor)
      {
        if (eNotificationRequired())
          eNotify(new ENotificationImpl(this, Notification.RESOLVE, ...));
      }
    }
    return author;
  }

Αντί για την επιστροφή απλά της μεταβλητής χρήσης author, πρώτα καλείται η μεταβιβασμένη μέθοδος πλαισίου eIsProxy() που ελέγχει αν η παραπομπή είναι ένας αντιπρόσωπος. Στη συνέχεια, αν είναι, καλείται η μέθοδος eResolveProxy(). Η μέθοδος αυτή καλεί τη μέθοδο EcoreUtil.resolve(), μια στατική βοηθητική μέθοδο που επιχειρεί να φορτώσει το έγγραφο του αντικειμένου προορισμού, και κατά συνέπεια το ίδιο το αντικείμενο, χρησιμοποιώντας το URI του αντιπροσώπου. Αν το έγγραφο φορτωθεί με επιτυχία, η μέθοδος επιστρέφει το αναλυμένο αντικείμενο. Αν όμως η φόρτωση του εγγράφου αποτύχει, η μέθοδος θα επιστρέψει ξανά τον αντιπρόσωπο[6] .

Αμφίδρομες παραπομπές

Έχοντας κατανοήσει πώς η ανάλυση μιας οντότητας αντιπροσώπου (proxy) επηρεάζει το μοτίβο της μεθόδου get για ορισμένα είδη παραπομπών, μπορούμε τώρα να εξετάσουμε πώς επηρεάζεται το μοτίβο της μεθόδου set όταν μια συσχέτιση γίνεται αμφίδρομη. Ας αλλάξουμε τη μονόδρομη συσχέτιση του παραδείγματος μας ως εξής:

Αμφίδρομη παραπομπή: Ένας συγγραφέας (author) μπορεί να έχει γράψει 0 ή περισσότερα βιβλία, αλλά ένα βιβλίο μπορεί να έχει μόνο ένα συγγραφέα.

Η συσχέτιση είναι πλέον αμφίδρομη, όπως υποδηλώνει και η έλλειψη βέλους στο άκρο της γραμμής συσχέτισης προς το μέρος της κλάσης Writer. Το όνομα ρόλου που χρησιμοποιείται για την πρόσβαση στην κλάση Book από μια κλάση Writer είναι books.

Αν επαναδημιουργήσουμε το μοντέλο μας, η μέθοδος getAuthor() δεν θα επηρεαστεί, αλλά η μέθοδος setAuthor() θα δείχνει ως εξής:

  public void setAuthor(Writer newAuthor)
  {
    if (newAuthor != author)
    {
      NotificationChain msgs = null;
      if (author != null)
        msgs = ((InternalEObject)author).eInverseRemove(this, ..., msgs);
      if (newAuthor != null)
        msgs = ((InternalEObject)newAuthor).eInverseAdd(this, ..., msgs);
      msgs = basicSetAuthor(newAuthor, msgs);
      if (msgs != null) msgs.dispatch();
    }
    else if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, ...)); // send "touch" notification
  }

Όπως φαίνεται από την παραπομπή author στο παράδειγμά μας, όταν ορίζουμε μια αμφίδρομη παραπομπή, πρέπει επίσης να ορίσουμε το άλλο της άκρο καλώντας τη μέθοδο eInverseAdd(). Πρέπει επίσης να αφαιρέσουμε τυχόν προηγούμενους συγγραφείς καλώντας τη μέθοδο eInverseRemove(). Αυτό είναι απαραίτητο γιατί η παραπομπή author στο μοντέλο μας αναφέρεται πάντα σε μία οντότητα, δηλαδή ένα βιβλίο μπορεί να έχει μόνο ένα συγγραφέα[7]  και, κατά συνέπεια, δεν μπορεί να βρίσκεται σε περισσότερες από μία παραπομπές books μιας κλάσης Writer. Τέλος, πρέπει να ορίσουμε την παραπομπή author καλώντας μια άλλη δημιουργημένη μέθοδο [basicSetAuthor()] ως εξής:

  public NotificationChain basicSetAuthor(Writer newAuthor, NotificationChain msgs)
  {
    Writer oldAuthor = author;
    author = newAuthor;
    if (eNotificationRequired())
    {
      ENotificationImpl notification = new ENotificationImpl(this, ...);
      if (msgs == null) msgs = notification; else msgs.add(notification);
    }
    return msgs;
  }

Αυτή η μέθοδος είναι παρόμοια με τη μέθοδο set για τις μονόδρομες παραπομπές, με τη διαφορά ότι όταν το όρισμα msgs είναι μη null, η ειδοποίηση προστίθεται σε αυτό αντί να ενεργοποιείται απευθείας[8] . Λόγω όλων των ενεργειών προώθησης/επιστροφής και προσθήκης/αφαίρεσης κατά την εκτέλεση μιας μεθόδου set για μια αμφίδρομη παραπομπή, μπορούν να δημιουργηθούν έως και τέσσερις (τρεις στο παράδειγμά μας) ειδοποιήσεις. Μια διεπαφή NotificationChain συλλέγει όλες αυτές τις ειδοποιήσεις, έτσι ώστε η ενεργοποίηση των ειδοποιήσεων να αναβληθεί μέχρι να ολοκληρωθούν όλες οι αλλαγές κατάστασης. Οι συγκεντρωμένες ειδοποιήσεις αποστέλλονται με την κλήση της μεθόδου msgs.dispatch(), όπως φαίνεται στη μέθοδο setAuthor() παραπάνω.

Παραπομπές πολλαπλών προορισμών

Παρατηρήσατε ενδεχομένως στο παράδειγμά μας ότι η συσχέτιση books (από την κλάση Writer στην κλάση Book) έχει χαρακτήρα "πολλαπλότητας προς πολλά" (δηλαδή, 0..*). Με άλλα λόγια, ένας συγγραφέας μπορεί να έχει γράψει περισσότερα από ένα βιβλία. Ο χειρισμός των παραπομπών πολλαπλών προορισμών (δηλαδή, οποιασδήποτε παραπομπής με ανώτερο όριο μεγαλύτερο από 1) στο EMF γίνεται με τη χρήση ενός API συλλογής, έτσι ώστε να δημιουργείται μόνο μία μέθοδος get στη διεπαφή:

  public interface Writer extends EObject
  {
    ...
    EList getBooks();
  }

Παρατηρήστε ότι η μέθοδος getBooks() επιστρέφει μια διεπαφή EList αντί μιας διεπαφής java.util.List. Στην ουσία, αυτές οι διεπαφές είναι ίδιες. Η διεπαφή EList είναι μια υποκλάση EMF της διεπαφής java.util.List που προσθέτει δύο μεθόδους μετακίνησης (move) στο API. Εκτός από αυτή τη διαφορά, από πλευράς πελάτη, μπορεί να θεωρηθεί ως μια τυπική λίστα Java. Για παράδειγμα, για την προσθήκη ενός βιβλίου στη συσχέτιση books, πρέπει να γίνει η ακόλουθη κλήση:

  aWriter.getBooks().add(aBook);

Αντίστοιχα, για την επανάληψη, θα πρέπει να γίνει το εξής:

  for (Iterator iter = aWriter.getBooks().iterator(); iter.hasNext(); )
  {
    Book book = (Book)iter.next();
    ...
  }

Όπως βλέπετε, από πλευράς πελάτη, το API για το χειρισμό των παραπομπών πολλαπλών προορισμών δεν είναι κάτι ιδιαίτερο. Ωστόσο, επειδή η παραπομπή books είναι μέρος μιας αμφίδρομης συσχέτισης (το αντίστροφο μέρος του Book.author), απαιτείται η εφαρμογή της ίδιας αντίστροφης χειραψίας που παρουσιάσαμε για τη μέθοδο setAuthor(). Παρατηρώντας την υλοποίηση της μεθόδου getBooks() στην κλάση υλοποίησης WriterImpl, μπορούμε να δούμε πώς γίνεται ο χειρισμός των παραπομπών πολλαπλών προορισμών:

  public EList getBooks()
  {
    if (books == null)
    {
      books = new EObjectWithInverseResolvingEList(Book.class, this,
                    LibraryPackage.WRITER__BOOKS, LibraryPackage.BOOK__AUTHOR);
    }
    return books;
  }

Η μέθοδος getBooks() επιστρέφει μια ειδική κλάση υλοποίησης, την κλάση EObjectWithInverseResolvingEList, η οποία δημιουργείται με όλες τις πληροφορίες που απαιτούνται για την πραγματοποίηση της αντίστροφης χειραψίας κατά τις κλήσεις προσθήκης και αφαίρεσης. Το EMF παρέχει 20 διαφορετικές υλοποιήσεις EList[9]  που καλύπτουν όλα τα είδη λειτουργιών με χαρακτήρα "πολλαπλότητας προς πολλά". Για τις μονόδρομες συσχετίσεις (δηλαδή, τις συσχετίσεις χωρίς αντίστροφο μέρος), χρησιμοποιούμε την κλάση EObjectResolvingEList. Αν η παραπομπή δεν χρειάζεται ανάλυση αντιπροσώπου (proxy), χρησιμοποιούμε την κλάση EObjectWithInverseEList ή την κλάση EObjectEList, κ.ο.κ.

Στο παράδειγμά μας, η λίστα που χρησιμοποιείται για την υλοποίηση της παραπομπής books δημιουργείται με το όρισμα LibraryPackage.BOOK__AUTHOR (μια παραγόμενη στατική σταθερά int που αντιπροσωπεύει την αντίστροφη λειτουργία). Χρησιμοποιείται κατά την κλήση add() για την κλήση της μεθόδου eInverseAdd() στην κλάση Book, ακριβώς όπως συνέβη στην κλάση Writer κατά την κλήση setAuthor(). Η μέθοδος eInverseAdd() στην κλάση BookImpl έχει την ακόλουθη μορφή:

  public NotificationChain eInverseAdd(InternalEObject otherEnd, int featureID,
                                       Class baseClass, NotificationChain msgs)
  {
    if (featureID >= 0)
    {
      switch (eDerivedStructuralFeatureID(featureID, baseClass))
      {
        case LibraryPackage.BOOK__AUTHOR:
          if (author != null)
            msgs = ((InternalEObject)author).eInverseRemove(this, .., msgs);
          return basicSetAuthor((Writer)otherEnd, msgs);
        default:
          ...
      }
    }
    ...
  }

Αρχικά, καλείται η μέθοδος eInverseRemove() για την αφαίρεση τυχόν προηγούμενων συγγραφέων [όπως περιγράψαμε παραπάνω κατά την παρουσίαση της μεθόδου setAuthor()]. Στη συνέχεια, καλείται η μέθοδος basicSetAuthor() που ορίζει την παραπομπή. Παρόλο που το συγκεκριμένο παράδειγμα έχει μόνο μία αμφίδρομη παραπομπή, η μέθοδος eInverseAdd() χρησιμοποιεί μια πρόταση switch που περιλαμβάνει μια περίπτωση για κάθε αμφίδρομη παραπομπή στην κλάση Book[10] .

Παραπομπές περίεξης

Ας προσθέσουμε μια νέα κλάση με το όνομα Library. Αυτή η κλάση θα λειτουργεί ως περιέκτης (θέση υποδοχής) για βιβλία (Books).

Παραπομπή περίεξης: Μια βιβλιοθήκη (Library) περιέχει 0 ή περισσότερα βιβλία.

Η παραπομπή περίεξης δηλώνεται με έναν μαύρο ρόμβο στο άκρο Library της συσχέτισης. Γενικά, αυτή η συσχέτιση δηλώνει ότι μια βιβλιοθήκη (Library) συγκεντρώνει βάσει τιμής 0 ή περισσότερα βιβλία. Οι συσχετίσεις συγκέντρωσης βάσει τιμής (περίεξη) είναι ιδιαίτερα σημαντικές γιατί αναγνωρίζουν το γονικό στοιχείο ή τον κάτοχο μιας χρήσης προορισμού, υποδηλώνοντας έτσι τη φυσική θέση του αντικειμένου κατά τη μονιμοποίηση.

Η περίεξη (containment) επηρεάζει τον δημιουργούμενο κώδικα με διάφορους τρόπους. Πρώτα απ' όλα, καθώς ένα περιεχόμενο αντικείμενο είναι βέβαιο ότι βρίσκεται στον ίδιο πόρο με τον περιέκτη του, δεν απαιτείται ανάλυση αντιπροσώπου (proxy). Ως εκ τούτου, η δημιουργούμενη μέθοδος get στην κλάση υλοποίησης LibraryImpl θα χρησιμοποιεί μια κλάση υλοποίησης EList που δεν εκτελεί ανάλυση:

  public EList getBooks()
  {
    if (books == null)
    {
      books = new EObjectContainmentEList(Book.class, this, ...);
    }
    return books;
  }

Επιπλέον, πέρα από το δεν εκτελεί ανάλυση αντιπροσώπου, η κλάση EObjectContainmentEList υλοποιεί τη λειτουργία contains() με πολύ αποδοτικό τρόπο (δηλαδή, σε σταθερό χρόνο και όχι γραμμικά). Αυτό είναι ιδιαίτερα σημαντικό γιατί η ύπαρξη διπλότυπων καταχωρήσεων στις λίστες παραπομπών του EMF δεν υποστηρίζεται, οπότε η λειτουργία contains() καλείται επίσης και κατά την εκτέλεση των λειτουργιών add().

Επειδή ένα αντικείμενο μπορεί να έχει μόνο έναν περιέκτη, η προσθήκη ενός αντικειμένου σε μια συσχέτιση περίεξης σημαίνει ότι το αντικείμενο αφαιρείται από τον τρέχοντα περιέκτη του, ανεξάρτητα από τη συσχέτιση. Για παράδειγμα, η προσθήκη ενός βιβλίου (Book) στη λίστα βιβλίων μιας βιβλιοθήκης (Library) μπορεί να προϋποθέτει την αφαίρεση του βιβλίου από τη λίστα βιβλίων κάποιας άλλης βιβλιοθήκης. Το ίδιο ισχύει για κάθε αμφίδρομη συσχέτιση στην οποία το αντίστροφο μέλος έχει πολλαπλότητα 1. Ας υποθέσουμε όμως ότι υπήρχε ακόμα μία συσχέτιση περίεξης με όνομα ownedBooks ανάμεσα στην κλάση Writer και την κλάση Book. Σε αυτή την περίπτωση, αν ένα βιβλίο περιλαμβανόταν στη λίστα ownedBooks κάποιου συγγραφέα (Writer), θα έπρεπε πρώτα να αφαιρεθεί από τη λίστα του συγγραφέα για να προστεθεί στη λίστα βιβλίων της βιβλιοθήκης (Library).

Για να διασφαλιστεί η αποδοτική υλοποίηση σε τέτοιες περιπτώσεις, η βασική κλάση EObjectImpl έχει μια μεταβλητή χρήσης (eContainer) του είδους EObject που χρησιμοποιείται για τη γενική αποθήκευση των περιεκτών. Ως εκ τούτου, οι παραπομπές περίεξης είναι πάντα έμμεσα αμφίδρομες. Για την πρόσβαση στη βιβλιοθήκη (Library) από ένα βιβλίο (Book), ο κώδικας θα πρέπει να έχει την ακόλουθη μορφή:

  EObject container = book.eContainer();
  if (container instanceof Library)
    library = (Library)container;

Αν θέλετε να αποφύγετε τον υποβιβασμό (downcast), μπορείτε να αλλάξετε τη συσχέτιση ώστε να είναι ρητά αμφίδρομη:

Αμφίδρομη παραπομπή περίεξης: Μια βιβλιοθήκη (Library) περιέχει 0 ή περισσότερα βιβλία. Τα βιβλία βρίσκονται σε μια βιβλιοθήκη.

Το EMF μπορεί έτσι να δημιουργήσει μια typesafe μέθοδο get :

  public Library getLibrary()
  {
    if (eContainerFeatureID != LibraryPackage.BOOK__LIBRARY) return null;
    return (Library)eContainer;
  }

Παρατηρήστε ότι η ρητή μέθοδος get χρησιμοποιεί τη μεταβλητή eContainer από την κλάση υλοποίησης EObjectImpl και όχι μια δημιουργούμενη μεταβλητή χρήσης, όπως είδαμε να συμβαίνει παραπάνω για τις παραπομπές που δεν αποτελούν περιέκτες [π.χ. getAuthor()][11] .

Γνωρίσματα απαρίθμησης

Μέχρι τώρα, είδαμε πώς το EMF χειρίζεται απλά γνωρίσματα και διάφορα είδη παραπομπών. Ένα άλλο συχνά χρησιμοποιούμενο είδος γνωρισμάτων είναι οι απαριθμήσεις. Τα γνωρίσματα αυτού του είδους υλοποιούνται με τη χρήση του typesafe μοτίβου απαριθμήσεων Java[12] .

Ας προσθέσουμε ένα γνώρισμα απαρίθμησης με όνομα Category στην κλάση Book:

Γνώρισμα απαρίθμησης και ορισμός: Η κλάση Book έχει μια κατηγορία κλάσης BookCategory που είναι μια απαρίθμηση με τιμές Mystery, ScienceFiction και Biography.

Αν επαναδημιουργήσουμε τις κλάσεις υλοποίησης, η διεπαφή Book θα περιλαμβάνει μια μέθοδο get και μια μέθοδο set για το γνώρισμα κατηγορίας (Category):

  BookCategory getCategory();
  void setCategory(BookCategory value);

Στη διεπαφή που θα δημιουργηθεί, οι μέθοδοι κατηγορίας χρησιμοποιούν μια typesafe κλάση απαρίθμησης με όνομα BookCategory. Αυτή η κλάση ορίζει στατικές σταθερές για τις τιμές απαρίθμησης και άλλες πρακτικές μεθόδους ως εξής:

  public final class BookCategory extends AbstractEnumerator
  {
    public static final int MYSTERY = 0;
    public static final int SCIENCE_FICTION = 1;
    public static final int BIOGRAPHY = 2;

    public static final BookCategory MYSTERY_LITERAL =
      new BookCategory(MYSTERY, "Mystery");
    public static final BookCategory SCIENCE_FICTION_LITERAL =
      new BookCategory(SCIENCE_FICTION, "ScienceFiction");
    public static final BookCategory BIOGRAPHY_LITERAL =
      new BookCategory(BIOGRAPHY, "Biography");

    public static final List VALUES = Collections.unmodifiableList(...));

    public static BookCategory get(String name)
    {
      ...
    }

    public static BookCategory get(int value)
    {
      ...
    }

    private BookCategory(int value, String name)
    {
      super(value, name);
    }
  }

Όπως φαίνεται, η κλάση απαρίθμησης παρέχει στατικές σταθερές int για τις τιμές της απαρίθμησης καθώς και στατικές σταθερές για τα ίδια τα αντικείμενα singleton λεκτικών μονάδων της απαρίθμησης. Τα ονόματα των σταθερών int είναι ίδια με τα ονόματα λεκτικών μονάδων του μοντέλου [13] . Οι σταθερές λεκτικών μονάδων έχουν τα ίδια ονόματα συνοδευόμενα από το επίθημα _LITERAL.

Οι σταθερές διευκολύνουν την πρόσβαση στις λεκτικές μονάδες σε διάφορες περιπτώσεις, όπως για παράδειγμα κατά τον ορισμό της κατηγορίας ενός βιβλίου:

  book.setCategory(BookCategory.SCIENCE_FICTION_LITERAL);

Η λειτουργία κατασκευής BookCategory είναι ιδιωτική και επομένως οι μόνες χρήσεις της κλάσης απαρίθμησης που μπορούν να υπάρξουν είναι αυτές που χρησιμοποιούνται για τις στατικές σταθερές MYSTERY_LITERAL, SCIENCE_FICTION_LITERAL και BIOGRAPHY_LITERAL. Ως αποτέλεσμα, δεν απαιτείται ποτέ η πραγματοποίηση συγκρίσεων ισότητας [δηλαδή, κλήσεις .equals()]. Αξιόπιστη σύγκριση των λεκτικών μονάδων μπορεί να γίνει με τη χρήση του απλούστερου και πιο αποδοτικού τελεστή ==, όπως ακριβώς φαίνεται παρακάτω:

  book.getCategory() == BookCategory.MYSTERY_LITERAL

Κατά τη σύγκριση με πολλαπλές τιμές, είναι ακόμα καλύτερα να χρησιμοποιείται μια πρόταση switch με τις τιμές int:

  switch (book.getCategory().value()) {
    case BookCategory.MYSTERY:
      // do something ...
      break;
    case BookCategory.SCIENCE_FICTION:
      ...
  }

Όταν η μόνη διαθέσιμη πληροφορία είναι το όνομα λεκτικής μονάδας (String) ή η τιμή (int), στην κλάση απαρίθμησης δημιουργούνται επίσης εύχρηστες μέθοδοι get() που μπορούν να χρησιμοποιηθούν για την ανάκτηση του αντίστοιχων αντικειμένων λεκτικών μονάδων.

Μέθοδοι κατασκευής και πακέτα

Εκτός από τις διεπαφές του μοντέλου και τις κλάσεις υλοποίησης, το EMF δημιουργεί τουλάχιστον ακόμα δύο διεπαφές (και κλάσεις υλοποίησης). Πρόκειται για μια μέθοδο κατασκευής και ένα πακέτο.

Η μέθοδος κατασκευής (factory) χρησιμοποιείται για τη δημιουργία των χρήσεων των κλάσεων του μοντέλου σας, ενώ το πακέτο (package) παρέχει πρόσθετες στατικές σταθερές (π.χ. τις σταθερές λειτουργιών που χρησιμοποιούνται από τις δημιουργούμενες μεθόδους) και εύχρηστες μεθόδους για την πρόσβαση στα μεταδεδομένα του μοντέλου σας[14] .

Η διεπαφή Factory για το παράδειγμά μας με τα βιβλία είναι η εξής:

  public interface LibraryFactory extends EFactory
  {
    LibraryFactory eINSTANCE = new LibraryFactoryImpl();

    Book createBook();
    Writer createWriter();
    Library createLibrary();

    LibraryPackage getLibraryPackage();
  }

Όπως φαίνεται, η δημιουργούμενη διεπαφή Factory παρέχει μια μέθοδο κατασκευής (create) για κάθε κλάση που ορίζεται στο μοντέλο, μια μέθοδο πρόσβασης για το πακέτο του μοντέλου, και μια παραπομπή στατικής σταθεράς (eINSTANCE) στο singleton μεθόδου κατασκευής.

Η διεπαφή LibraryPackage επιτρέπει τη εύκολη πρόσβαση σε όλα τα μεταδεδομένα του μοντέλου μας:

  public interface LibraryPackage extends EPackage
  {
    ...
    LibraryPackage eINSTANCE = LibraryPackageImpl.init();

    static final int BOOK = 0;
    static final int BOOK__TITLE = 0;
    static final int BOOK__PAGES = 1;
    static final int BOOK__CATEGORY = 2;
    static final int BOOK__AUTHOR = 3;
    ...

    static final int WRITER = 1;
    static final int WRITER__NAME = 0;
    ...

    EClass getBook();
    EAttribute getBook_Title();
    EAttribute getBook_Pages();
    EAttribute getBook_Category();
    EReference getBook_Author();

    ...
  }

Όπως μπορείτε να δείτε, τα μεταδεδομένα είναι διαθέσιμα ως σταθερές int και ως μετα-αντικείμενα Ecore. Οι σταθερές int αποτελούν τον αποτελεσματικότερο τρόπο για τη μεταβίβαση μετα-πληροφοριών. Θα έχετε ίσως παρατηρήσει ήδη ότι οι δημιουργούμενες μέθοδοι χρησιμοποιούν αυτές τις σταθερές στις υλοποιήσεις τους. Αργότερα, όταν εξετάσουμε τον τρόπο με τον οποίο μπορούν να υλοποιηθούν οι προσαρμογείς του EMF, θα διαπιστώσετε ότι οι σταθερές αποτελούν επίσης τον αποτελεσματικότερο τρόπο για τον προσδιορισμό των αλλαγών κατά το χειρισμό των ειδοποιήσεων. Επιπλέον, όπως η διεπαφή Factory, η δημιουργούμενη διεπαφή Package παρέχει μια παραπομπή στατικής σταθεράς στο singleton υλοποίησής της.

Δημιουργία κλάσεων με υπερκλάσεις

Ας δημιουργήσουμε μια υποκλάση με όνομα SchoolBook στην κλάση του μοντέλου Book:

Απλή μεταβίβαση: Μια κλάση SchoolBook επεκτείνει την κλάση Book.

Η γεννήτρια του EMF χειρίζεται την απλή μεταβίβαση με τον αναμενόμενο τρόπο. Η δημιουργούμενη διεπαφή επεκτείνει την αντίστοιχη υπερδιεπαφή:

  public interface SchoolBook extends Book

Αντίστοιχα, η δημιουργούμενη κλάση υλοποίησης επεκτείνει την αντίστοιχη υπερκλάση υλοποίησης:

  public class SchoolBookImpl extends BookImpl implements SchoolBook

Όπως και η Java, το EMF υποστηρίζει την πολλαπλή μεταβίβαση, με τη διαφορά ότι κάθε κλάση στο EMF μπορεί να επεκτείνει μόνο μία βασική κλάση υλοποίησης. Για το λόγο αυτό, όταν έχουμε ένα μοντέλο με πολλαπλή μεταβίβαση, πρέπει να προσδιορίσουμε ποια από τις διεπαφές βάσης θα χρησιμοποιείται ως βασική κλάση υλοποίησης. Οι υπόλοιπες θα αντιμετωπιστούν απλά ως διεπαφές μίξης και οι υλοποιήσεις τους θα δημιουργηθούν στην κλάση υλοποίησης που θα προκύψει.

Δείτε το παρακάτω παράδειγμα:

Πολλαπλή μεταβίβαση: Μια κλάση SchoolBook επεκτείνει την κλάση Book και την κλάση Asset. (Η κλάση Asset έχει μια τιμή float.)

Σε αυτό το παράδειγμα, η κλάση SchoolBook προέρχεται από δύο κλάσεις, την κλάση Book και την κλάση Asset. Όπως μπορείτε να δείτε, η κλάση Book έχει οριστεί ως η βασική κλάση υλοποίησης[15] . Αν επαναδημιουργήσουμε το μοντέλο, η διεπαφή SchoolBook θα επεκτείνει τις δύο διεπαφές:

  public interface SchoolBook extends Book, Asset

Η κλάση υλοποίησης έχει την ίδια μορφή που είχε παραπάνω, με τη διαφορά ότι τώρα περιλαμβάνει υλοποιήσεις των mixin μεθόδων getValue() και setValue():

  public class SchoolBookImpl extends BookImpl implements SchoolBook
  {
    public float getValue()
    {
      ...
    }

    public void setValue(float newValue)
    {
      ...
    }

    ...
  }

Προσαρμογή των δημιουργημένων κλάσεων υλοποίησης

Μπορείτε να προσθέσετε συμπεριφορά (μεθόδους και μεταβλητές χρήσεων) στις δημιουργημένες κλάσεις Java χωρίς να χάσετε τις αλλαγές σας σε περίπτωση που αργότερα αποφασίσετε να τροποποιήσετε το μοντέλο και να το επαναδημιουργήσετε. Για παράδειγμα, ας προσθέσουμε μια μέθοδο isRecommended() στην κλάση Book. Για να γίνει αυτό, απλά προσθέστε τη νέα υπογραφή μεθόδου στην κλάση Book του περιβάλλοντος Java:

  public interface Book ...
  {
    boolean isRecommended();
    ...
  }

και την υλοποίησή της στην κλάση BookImpl:

  public boolean isRecommended()
  {
    return getAuthor().getName().equals("William Shakespeare");
  }

Η γεννήτρια EMF δεν αφαιρεί την αλλαγή καθώς δεν πρόκειται για εξ’ αρχής δημιουργημένη μέθοδο. Κάθε μέθοδος που δημιουργείται από το EMF περιλαμβάνει ένα σχόλιο Javadoc το οποίο περιέχει ένα προσδιοριστικό @generated, ως εξής:

  /**
   * ...
   * @generated
   */
  public String getTitle()
  {
    return title;
  }

Οποιαδήποτε μέθοδος στο αρχείο δεν περιέχει αυτό το προσδιοριστικό (like isRecommended()) θα παραμείνει ως έχει κατά την επαναδημιουργία στοιχείων. Ουσιαστικά, αν επιθυμείτε να αλλάξετε την υλοποίηση μιας δημιουργημένης μεθόδου, αυτό μπορεί να γίνει με την αφαίρεση του προσδιοριστικού @generated από τη μέθοδο[16] :

  /**
   * ...
   * @generated // (αφαιρέθηκε)
   */
  public String getTitle()
  {
    // προσαρμοσμένη υλοποίηση ...
  }

Επειδή λείπει το προσδιοριστικό @generated, η μέθοδος getTitle() θεωρείται κώδικας του χρήστη. Αν δημιουργηθεί εκ νέου το μοντέλο, η γεννήτρια θα εντοπίσει τη διένεξη και απλά θα απορρίψει τη δημιουργημένη εκδοχή της μεθόδου.

Πριν απορριφθεί μια δημιουργημένη μέθοδος, η γεννήτρια ελέγχει πρώτα αν υπάρχει άλλη δημιουργημένη μέθοδος με το ίδιο όνομα στο αρχείο, στο οποίο όμως έχει προστεθεί η σειρά χαρακτήρων Gen. Αν το εντοπίσει, αντί να απορρίψει τη νέα δημιουργημένη έκδοση της μεθόδου, ανακατευθύνει τα στοιχεία εξόδου της σε αυτό το αρχείο. Για παράδειγμα, για να επεκτείνετε τη δημιουργημένη υλοποίηση getTitle(), αντί να την απορρίψετε μπορείτε να την μετονομάσετε ως εξής:

  /**
   * ...
   * @generated
   */
  public String getTitleGen()
  {
    return title;
  }

και στη συνέχεια να προσθέσετε μια μέθοδο χρήστη που εκτελεί τις επιθυμητές ενέργειες:

  public String getTitle()
  {
    String result = getTitleGen();
    if (result == null)
      result = ...
    return result;
  }

Αν κάνετε επαναδημιουργία τώρα, η γεννήτρια θα εντοπίσει τη διένεξη με την εκδοχή της μεθόδου getTitle() του χρήστη, αλλά επειδή υπάρχει επίσης η μέθοδος @generated getTitleGen() στην κλάση θα ανακατευθύνει σε αυτή τη νέα δημιουργημένη υλοποίηση αντί να την απορρίψει.

Λειτουργίες σε μοντέλα EMF

Εκτός από τα γνωρίσματα και τις παραπομπές, μπορείτε να προσθέσετε λειτουργίες στις κλάσεις μοντέλων. Σε αυτή την περίπτωση, η γεννήτρια EMF θα δημιουργήσει την υπογραφή τους στο περιβάλλον και μια δομή μεθόδου στην κλάση υλοποίηση. Το EMF δεν μοντελοποιεί συμπεριφορά, συνεπώς η υλοποίηση πρέπει να παρέχεται από κώδικα Java που συντάσσεται από τον χρήστη.

Αυτό μπορεί να γίνει με την αφαίρεση του προσδιοριστικού @generated από τη δημιουργημένη υλοποίηση, όπως περιγράφεται παραπάνω, και με την προσθήκη του κώδικα σε εκείνο το σημείο. Εναλλακτικά, ο κώδικας Java μπορεί να συμπεριληφθεί απευθείας στο μοντέλο. Στο Rose, μπορείτε να τον καταχωρήσετε στο πλαίσιο κειμένου στην καρτέλα Semantics του πλαισίου διαλόγου Operation Specification. Ο κώδικας θα αποθηκευτεί στο μοντέλο EMF ως σημείωση στη λειτουργία[17] , και θα δημιουργηθεί στο κύριο σώμα.

Χρήση των δημιουργημένων κλάσεων EMF

Δημιουργία χρήσεων και πρόσβαση

Με χρήση των δημιουργημένων κλάσεων, ένα πρόγραμμα-πελάτης μπορεί να δημιουργήσει και να αποδώσει αρχικές τιμές σε μια κλάση Book με τις ακόλουθες απλές προτάσεις Java:

  LibraryFactory factory = LibraryFactory.eINSTANCE;

  Book book = factory.createBook();

  Writer writer = factory.createWriter();
  writer.setName("William Shakespeare");

  book.setTitle("King Lear");
  book.setAuthor(writer);

Επειδή η συσχέτιση Book με Writer (συγγραφέας) είναι αμφίδρομη, γίνεται αυτόματα απόδοση τιμών και στην αντίστροφη παραπομπή (βιβλία). Η επαλήθευση μπορεί να γίνει με την επανάληψη της παραπομπής βιβλίων, ως εξής:

  System.out.println("Shakespeare books:");
  for (Iterator iter = writer.getBooks().iterator(); iter.hasNext(); )
  {
    Book shakespeareBook = (Book)iter.next();
    System.out.println("  title: " + shakespeareBook.getTitle());
  }

Η εκτέλεση του προγράμματος θα επέστρεφε δεδομένα εξόδου παρόμοια με τα εξής:

  Shakespeare books:
    title: King Lear

Αποθήκευση και φόρτωση πόρων

Για να δημιουργήσετε ένα έγγραφο με όνομα mylibrary.xmi που θα περιέχει το παραπάνω μοντέλο, το μόνο που χρειάζεται είναι η δημιουργία ενός πόρου EMF στην αρχή του προγράμματος, η καταχώρηση του βιβλίου και του συγγραφέα στον πόρο, και η κλήση της μεθόδου save() στο τέλος:

  // Create a resource set.
  ResourceSet resourceSet = new ResourceSetImpl();

  // Register the default resource factory -- only needed for stand-alone!
  resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());

  // Get the URI of the model file.
  URI fileURI = URI.createFileURI(new File("mylibrary.xmi").getAbsolutePath());

  // Create a resource for this file.
  Resource resource = resourceSet.createResource(fileURI);

  // Add the book and writer objects to the contents.
  resource.getContents().add(book);
  resource.getContents().add(writer);

  // Save the contents of the resource to the file system.
  try
  {
    resource.save(Collections.EMPTY_MAP);
  }
  catch (IOException e) {}

Έχετε υπόψη ότι για τη δημιουργία του πόρου EMF χρησιμοποιείται ένα σύνολο πόρων (περιβάλλον ResourceSet). Ένα σύνολο πόρων χρησιμοποιείται από το πλαίσιο EMF για τη διαχείριση πόρων που ίσως περιέχουν παραπομπές σε εξωτερικά έγγραφα. Με χρήση ενός μητρώου (περιβάλλον Resource.Factory.Registry), δημιουργείται το κατάλληλο είδος πόρου για ένα καθορισμένο URI με βάση το σχήμα του, την επέκταση αρχείων ή άλλα πιθανά κριτήρια. Σε αυτό το σημείο, εγγράφεται η υλοποίηση πόρου XMI ως προεπιλογή για αυτό το σύνολο πόρων[18] . Κατά τη φόρτωση, το σύνολο πόρων διαχειρίζεται επίσης την κατ’απαίτηση φόρτωση των παραπομπών εξωτερικών εγγράφων.

Με την εκτέλεση του προγράμματος θα δημιουργηθεί το αρχείο mylibrary.xmi με περιεχόμενα παρόμοια με τα εξής:

  <xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"
      xmlns:library="http:///library.ecore">
    <library:Book title="King Lear" author="/1"/>
    <library:Writer name="William Shakespeare" books="/0"/>
  </xmi:XMI>

Για να φορτώσετε το έγγραφο mylibrary.xmi που αποθηκεύτηκε παραπάνω, ρυθμίστε ένα σύνολο πόρων και φορτώστε κατ’ απαίτηση τον πόρο ως εξής:

   // Create a resource set.
   ResourceSet resourceSet = new ResourceSetImpl();

  // Register the default resource factory -- only needed for stand-alone!
  resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());

  // Register the package -- only needed for stand-alone!
  LibraryPackage libraryPackage = LibraryPackage.eINSTANCE;

   // Get the URI of the model file.
   URI fileURI = URI.createFileURI(new File("mylibrary.xmi").getAbsolutePath());

   // Demand load the resource for this file.
   Resource resource = resourceSet.getResource(fileURI, true);

   // Print the contents of the resource to System.out.
   try
   {
     resource.save(System.out, Collections.EMPTY_MAP);
   }
   catch (IOException e) {}

Ξανά, πρέπει να δημιουργήσετε ένα σύνολο πόρων και, στην περίπτωση μεμονωμένης χρήσης, να εγγράψετε μια προεπιλεγμένη υλοποίηση πόρων. Επιπλέον, πρέπει να εξασφαλιστεί ότι το πακέτο έχει εγγραφεί στο μητρώο πακέτων, το οποίο χρησιμοποιείται από τον πόρο για την ανάκτηση των κατάλληλων μεταδεδομένων και της μεθόδου κατασκευής για το μοντέλο που φορτώνεται. Η πρόσβαση στο πεδίο eINSTANCE μιας δημιουργημένης διεπαφής πακέτου αρκεί για να βεβαιωθείτε ότι έχει εγγραφεί.

Αυτό το παράδειγμα χρησιμοποιεί τη δεύτερη μορφή της μεθόδου save(), η οποία λαμβάνει ένα στοιχείο OutputStream, για να εμφανίσει τη σειριοποίηση στην κονσόλα.

Ο διαχωρισμός ενός μοντέλου σε πολλαπλά έγγραφα με παραπομπές μεταξύ εγγράφων, είναι απλή διαδικασία. Αν θελήσετε να σειριοποιήσετε τα βιβλία και τους συγγραφείς, στο παραπάνω παράδειγμα αποθήκευσης, το μόνο που χρειάζεται είναι να δημιουργηθεί ένας δεύτερος πόρος:

  Resource anotherResource = resourceSet.createResource(anotherFileURI);

και να προστεθεί ο συγγραφέας, αντί να δημιουργηθεί ο πρώτος πόρος:

  resource.getContents().add(writer); // (replaced)
  anotherResource.getContents().add(writer);

Με αυτό τον τρόπο δημιουργούνται δύο πόροι. Κάθε πόρος περιέχει ένα αντικείμενο με μια παραπομπή προς τον άλλο πόρο.

Έχετε υπόψη ότι μια παραπομπή περίεξης υπονοεί ότι το περιεχόμενο αντικείμενο βρίσκεται στον ίδιο πόρο με τον περιέκτη του. Συνεπώς, για παράδειγμα, έστω ότι δημιουργήσατε μια χρήση κλάσης Library που περιέχει ένα στοιχείο Book μέσω της παραπομπής περίεξης βιβλίων. Με αυτό τον τρόπο θα είχε αφαιρεθεί αυτόματα το στοιχείο Book από τα περιεχόμενα του πόρου, ο οποίος συμπεριφέρεται ως παραπομπή περίεξης. Αν στη συνέχεια προσθέσετε την κλάση Library στον πόρο, το βιβλίο θα ανήκει και στον πόρο και οι λεπτομέρειές του θα σειριοποιηθούν.

Αν το επιθυμείτε, μπορείτε να σειριοποιήσετε τα αντικείμενά σας σε μορφή διαφορετική της XMI. Θα χρειαστεί να καταχωρήσετε τον δικό σας κώδικα σειριοποίησης και ανάλυσης. Δημιουργήστε τη δική σας κλάση πόρων (ως υποκλάση του ResourceImpl) η οποία υλοποιεί την προτιμώμενη μορφή σειριοποίησης, και στη συνέχεια εγγράψτε τη τοπικά με το σύνολο πόρων ή με το γενικό μητρώο μεθόδων κατασκευής, αν επιθυμείτε να χρησιμοποιείται πάντα με το μοντέλο σας.

Παρατήρηση (προσαρμογή) αντικειμένων EMF

Στην περιγραφή των στατικών μεθόδων σε δημιουργημένες κλάσεις EMF, αναφέρθηκε ότι οι ειδοποιήσεις αποστέλλονται πάντα κατά την αλλαγή ενός γνωρίσματος ή μιας παραπομπής. Για παράδειγμα, η μέθοδος BookImpl.setPages() περιλάμβανε την ακόλουθη γραμμή:

  eNotify(newENotificationImpl(this, ..., oldPages, pages));

Κάθε EObject μπορεί να περιέχει μια λίστα παρατηρητών (αναφέρονται και ως προσαρμογείς), οι οποίοι ενημερώνονται όταν εντοπιστεί μια αλλαγή κατάστασης. Η μέθοδος eNotify() του πλαισίου εκτελείται επαναλαμβανόμενα σε αυτή τη λίστα και διαβιβάζει την ειδοποίηση στους παρατηρητές.

Ένας παρατηρητής μπορεί να συνδεθεί σε οποιοδήποτε EObject (για παράδειγμα, ένα βιβλίο) με την προσθήκη του στη λίστα eAdapters:

  Adapter bookObserver = ...
  book.eAdapters().add(bookObserver);

Συνήθως, οι προσαρμογείς προστίθενται σε αντικείμενα EObject με χρήση μιας μεθόδου κατασκευής προσαρμογέων. Εκτός από τον ρόλο του παρατηρητή, οι προσαρμογείς χρησιμοποιούνται γενικά ως μια μέθοδος επέκτασης της συμπεριφοράς του αντικειμένου στο οποίο έχουν συνδεθεί. Ένας πελάτης αποκτά την εκτεταμένη συμπεριφορά ζητώντας από μια μέθοδο κατασκευής προσαρμογέα την προσαρμογή ενός αντικειμένου με μια επέκταση του απαιτούμενου είδους. Συνήθως, έχει την ακόλουθη μορφή:

  EObject someObject = ...;
  AdapterFactory someAdapterFactory = ...;
  Object requiredType = ...;
  if(someAdapterFactory.isFactoryForType(requiredType))
  {
    Adapter theAdapter = someAdapterFactory.adapt(someObject, requiredType);
    ...
  }

Συνήθως, το requiredType αναπαριστά μια διεπαφή που υποστηρίζεται από τον προσαρμογέα. Για παράδειγμα, το όρισμα μπορεί να είναι το java.lang.Class για μια διεπαφή του επιλεγμένου προσαρμογέα. Ο επιστρεφόμενος προσαρμογέας μπορεί στη συνέχεια να μετατραπεί στη ζητούμενη διεπαφή, ως εξής:

  MyAdapter theAdapter =
    (MyAdapter)someAdapterFactory.adapt(someObject, MyAdapter.class);

Οι προσαρμογείς συνήθως χρησιμοποιούνται με αυτό τον τρόπο για να επεκτείνουν τη συμπεριφορά ενός αντικειμένου χωρίς τη δημιουργία υποκλάσεων.

Για να χειριστείτε τις ειδοποιήσεις σε ένα προσαρμογέα πρέπει να αντικαταστήσετε τη μέθοδο eNotifyChanged() η οποία καλείται σε κάθε εγγεγραμμένο προσαρμογέα από το eNotify(). ένας τυπικός προσαρμογέας υλοποιεί το eNotifyChanged() για την εκτέλεση κάποιας εργασίας για ορισμένες ή όλες τις ειδοποιήσεις, με βάση το είδος της ειδοποίησης.

Ορισμένοι προσαρμογείς έχουν σχεδιαστεί έτσι ώστε να προσαρμόζουν μια συγκεκριμένη κλάση (για παράδειγμα, Book). Σε αυτή την περίπτωση, η μέθοδος notifyChanged() ενδέχεται να έχει την ακόλουθη μορφή:

  public void notifyChanged(Notification notification)
  {
    Book book = (Book)notification.getNotifier();
    switch (notification.getFeatureID(Book.class))
    {
      case LibraryPackage.BOOK__TITLE:
        // book title changed
        doSomething();
        break;
      caseLibraryPackage.BOOK__CATEGORY:
        // book category changed
        ...
      case ...
    }
  }

Η κλήση για το notification.getFeatureID() μεταβιβάζεται στο όρισμα Book.class για το χειρισμό της πιθανότητας το αντικείμενο προς προσαρμογή να μην είναι χρήση της κλάσης BookImpl, αλλά χρήση μιας υποκλάσης πολλαπλών κληρονομήσεων στην οποία το στοιχείο Book δεν είναι η κύρια (πρώτη) διεπαφή. Σε αυτή την περίπτωση, η ταυτότητα λειτουργίας που μεταβιβάζεται στην ειδοποίηση θα είναι αριθμός που σχετίζεται με την άλλη κλάση και συνεπώς θα χρειαστεί να προσαρμοστεί προκειμένου να συνεχιστεί η χρήση των σταθερών BOOK__. Σε περιπτώσεις μεμονωμένης κληρονόμησης, αυτό το όρισμα παραβλέπεται.

Ένα άλλο συνηθισμένο είδος προσαρμογέα δεν συνδέεται σε συγκεκριμένη κλάση αλλά χρησιμοποιεί το EMF API για την εκτέλεση των ενεργειών. Αντί να καλέσει το getFeatureID() στην ειδοποίηση, μπορεί να καλέσει το getFeature(), το οποίο επιστρέφει τη λειτουργία Ecore (δηλαδή, το αντικείμενο στο μεταμοντέλο που αναπαριστά τη λειτουργία).

Χρήση του στοχαστικού API

Ο χειρισμός κάθε δημιουργημένης κλάσης μοντέλου μπορεί επίσης να γίνει με χρήση του στοχαστικού API που ορίζεται στη διεπαφή EObject:

  public interface EObject ...
  {
    ..
    Object eGet(EStructuralFeature feature);
    void eSet(EStructuralFeature feature, Object newValue);

    boolean eIsSet(EStructuralFeature feature);
    void eUnset(EStructuralFeature feature);
  }

Με χρήση του στοχαστικού API, μπορούμε να ορίσουμε το όνομα ενός συγγραφέα ως εξής:

  writer.eSet(LibraryPackage.eINSTANCE.getWriter_Name(), "William Shakespeare");

ή ανακτήστε το όνομα ως εξής:

  String name = (String)writer.eGet(LibraryPackage.eINSTANCE.getWriter_Name());

Έχετε υπόψη ότι η λειτουργία στην οποία γίνεται πρόσβαση αναγνωρίζεται από τα μεταδεδομένα που ανακτώνται από τη χρήση singleton του πακέτου βιβλιοθήκης.

Η χρήση του στοχαστικού API είναι ελάχιστα πιο αποτελεσματική από την απευθείας κλήση των δημιουργημένων μεθόδων getName() και setName()[19] , ωστόσο καθιστά το μοντέλο διαθέσιμο για γενική πρόσβαση. Για παράδειγμα, οι στοχαστικές μέθοδοι χρησιμοποιούνται από το πλαίσιο EMF.Edit για την υλοποίηση ενός πλήρους συνόλου γενικών εντολών (για παράδειγμα, AddCommand, RemoveCommand, SetCommand) που μπορούν να χρησιμοποιηθούν με οποιοδήποτε μοντέλο. Ανατρέξτε στο θέμα Επισκόπηση του EMF.Edit για λεπτομέρειες.

Εκτός από τα eGet() και eSet(), το στοχαστικό API περιλαμβάνει άλλες δύο σχετικές μεθόδους: eIsSet() και eUnset(). Η μέθοδος eIsSet() μπορεί να χρησιμοποιηθεί για να διαπιστωθεί αν ένα γνώρισμα έχει οριστεί [20] , ενώ η μέθοδος eUnset() μπορεί να χρησιμοποιηθεί για την αναίρεση του ορισμού (ή επαναφορά). Η γενική λειτουργία σειριοποίησης XMI, για παράδειγμα, χρησιμοποιεί τη μέθοδο eIsSet() για να προσδιορίσει τα γνωρίσματα που χρειάζονται σειριοποίηση κατά τη διάρκεια μιας διαδικασίας αποθήκευσης πόρων.

Ειδικά θέματα

Ενδείκτες ελέγχου δημιουργίας

Υπάρχουν διάφοροι ενδείκτες που μπορούν να οριστούν σε μια λειτουργία μοντέλου για τον έλεγχο του μοτίβου του δημιουργημένου κώδικα για τη συγκεκριμένη λειτουργία. Συνήθως, οι προεπιλεγμένες ρυθμίσεις αυτών των ενδεικτών είναι επαρκείς, συνεπώς ίσως δεν χρειαστεί να τις αλλάζετε συχνά.

Είδη δεδομένων

Όπως αναφέρθηκε νωρίτερα, όλες οι κλάσεις που ορίζονται σε ένα μοντέλο (για παράδειγμα, Book, Writer) προέρχονται έμμεσα από το EObject βασικής κλάσης EMF. Ωστόσο, όλες οι κλάσεις που χρησιμοποιεί ένα μοντέλο δεν είναι απαραίτητα αντικείμενα EObject. Για παράδειγμα, έστω ότι επιθυμείτε να προσθέσετε ένα γνώρισμα είδους java.util.Date to στο μοντέλο μας. Πριν γίνει αυτό, πρέπει να ορίσετε ένα είδος δεδομένων EMF το οποίο θα αναπαριστά το εξωτερικό είδος. Στο UML, χρησιμοποιήστε μια κλάση με το στερεότυπο είδους δεδομένων για αυτό το σκοπό:

Ορισμός είδους δεδομένων: το είδος δεδομένων JavaDate είναι κλάση java.util.Date

Όπως έχει αναφερθεί, ένα είδος δεδομένων είναι απλά ένα ονομασμένο στοιχείο στο μοντέλο που λειτουργεί ως αντιπρόσωπος για μια κλάση Java. Η πραγματική κλάση Java παρέχεται ως γνώρισμα με το στερεότυπο κλάσης java, με όνομα την πλήρη κλάση που αναπαρίσταται. Με αυτό το καθορισμένο είδος δεδομένων, μπορείτε να δηλώσετε γνωρίσματα είδους java.util.Date ως εξής:

Γνώρισμα με είδος δεδομένων ως είδος γνωρίσματος: Book has a publicationDate : JavaDate

Αν κάνετε επαναδημιουργία, το γνώρισμα publicationDate θα εμφανιστεί στη διεπαφή Book:

  import java.util.Date;

  public interface Book extends EObject
  {
    ...

    Date getPublicationDate();
    void setPublicationDate(Date value);
  }

Όπως μπορείτε να δείτε, ο χειρισμός αυτού του γνωρίσματος με είδος Ημερομηνία γίνεται όπως όλων των άλλων γνωρισμάτων. Όλα τα γνωρίσματα, συμπεριλαμβανομένων εκείνων με είδος Σειρά χαρακτήρων, Ακέραιος, κ.λπ. διαθέτουν είδος δεδομένων ως είδος. Η μόνη διαφορά με τα τυπικά είδη Java είναι ότι τα αντίστοιχα είδη δεδομένων προκαθορίζονται στο μοντέλο Ecore, συνεπώς δεν χρειάζεται να επανακαθοριστούν σε κάθε μοντέλο που τα χρησιμοποιεί.

Ένας ορισμός είδους δεδομένων διαθέτει άλλη μία επίδραση στο δημιουργημένο μοντέλο. Επειδή τα είδη δεδομένων αναπαριστούν μια αυθαίρετη κλάση, μια γενική λειτουργία σειριοποίησης ή ανάλυσης (για παράδειγμα, η προεπιλεγμένη λειτουργία σειριοποίησης XMI) δεν μπορεί να γνωρίζει τον τρόπο αποθήκευσης της κατάστασης ενός γνωρίσματος αυτού του είδους. Πρέπει να καλέσει το toString(); Αυτή είναι η λογική προεπιλογή, ωστόσο το πλαίσιο EMF δεν επιθυμεί να το ορίσει ως απαίτηση, συνεπώς δημιουργούνται δύο ακόμη μέθοδοι στην κλάση υλοποίησης μεθόδου κατασκευής για κάθε είδος δεδομένων που ορίζεται στο μοντέλο:

  /**
   * @generated
   */
  public Date createJavaDateFromString(EDataType eDataType, String initialValue)
  {
    return (Date)super.createFromString(eDataType, initialValue);
  }

  /**
   * @generated
   */
  public String convertJavaDateToString(EDataType eDataType, Object instanceValue)
  {
    return super.convertToString(eDataType, instanceValue);
  }

Από προεπιλογή, αυτές οι μέθοδοι απλά καλούν τις υλοποιήσεις υπερκλάσης, οι οποίες παρέχουν λογικές, αλλά αναποτελεσματικές, προεπιλεγμένες τιμές: το convertToString() απλά καλεί το toString() σε ένα στοιχείο instanceValue, αλλά το createFromString() προσπαθεί μέσω στοχαστικής ανάλυσης Java να καλέσει μια μέθοδο κατασκευής String ή, αν αυτό αποτύχει, μια στατική μέθοδο valueOf(), εφόσον υπάρχει. Συνήθως, πρέπει να αναλάβετε τον έλεγχο αυτών των μεθόδων (αφαιρώντας τα προσδιοριστικά @generated) και να τις αντικαταστήσετε με τις κατάλληλες προσαρμοσμένες υλοποιήσεις:

  /**
   * @generated // (removed)
   */
  public String convertJavaDateToString(EDataType eDataType, Object instanceValue)
  {
    return instanceValue.toString();
  )

Μοντέλο Ecore

Ακολουθεί η πλήρης ιεραρχία κλάσεων του μοντέλου Ecore (τα σκιασμένα πλαίσια είναι αφηρημένες κλάσεις):

Ιεραρχία κλάσεων Ecore

Η ιεραρχία περιλαμβάνει τις κλάσεις που αναπαριστούν τα στοιχεία μοντέλου EMF που περιγράφονται στο παρόν έγγραφο: κλάσεις (και τα γνωρίσματά, οι παραπομπές και οι λειτουργίες τους), είδη δεδομένων, απαριθμήσεις, πακέτα και μέθοδοι κατασκευής.

Η υλοποίηση EMF του Ecore δημιουργείται με χρήση της γεννήτριας EMF, συνεπώς διαθέτει την ίδια περιορισμένων λειτουργιών αλλά αποτελεσματική υλοποίηση που περιγράφεται στις προηγούμενες ενότητες του εγγράφου.



[1] Στην πραγματικότητα, το μεταμοντέλο EMF είναι ένα μοντέλο EMF, του οποίου η προεπιλεγμένη σειριοποιημένη μορφή είναι XMI.

[2] Το EMF υποστηρίζει την εισαγωγή από το Rational Rose, ωστόσο η αρχιτεκτονική της γεννήτριας μπορεί εύκολα να αξιοποιήσει και άλλα εργαλεία μοντελοποίησης.

[3] Το EMF χρησιμοποιεί ένα υποσύνολο της απλών μοτίβων ονοματοθεσίας της λειτουργίας πρόσβασης σε ιδιότητες JavaBean.

[4] Υπάρχουν διάφορες επιλογές που ορίζονται από τον χρήστη και οι οποίες μπορούν να χρησιμοποιηθούν για την αλλαγή των δημιουργημένων μοτίβων. Αργότερα θα περιγραφούν ορισμένες από αυτές (βλ. Ενδείκτες ελέγχου δημιουργίας στο παρόν έγγραφο).

[5] Οι παραπομπές περίεξης, οι οποίες θα περιγραφούν αργότερα (βλ. Παραπομπές περίεξης), δεν μπορούν να παραπέμπουν σε άλλα έγγραφα. Υπάρχει επίσης ένας ενδείκτης τον οποίο μπορούν να ορίσουν οι χρήστες στα μεταδεδομένα μιας παραπομπής για να υποδείξουν ότι δεν χρειάζεται να γίνει ανάλυση καθώς η παραπομπή δεν θα χρησιμοποιηθεί ποτέ σε σενάριο χρήσης πολλαπλών εγγράφων (βλ. Ενδείκτες ελέγχου δημιουργίας). Σε αυτές τις περιπτώσεις, η δημιουργημένη μέθοδος λήψης απλά επιστρέφει τον δείκτη.

[6] Οι εφαρμογές που πρέπει να χειριστούν εσφαλμένες διασυνδέσεις πρέπει να καλέσουν το eIsProxy() στο αντικείμενο που επιστρέφεται από τη μέθοδο λήψης, για να διαπιστωθεί αν έχει αναλυθεί (για παράδειγμα, book.getAuthor().eIsProxy()).

[7] Με αυτό τον τρόπο δεν είναι δυνατός ο ορισμός πολλαπλών συγγραφέων, ωστόσο διατηρείται η απλή δομή του παραδείγματος μοντέλου.

[8] Η ανάθεση στη μέθοδο basicSet() γίνεται επειδή είναι απαραίτητη από τις μεθόδους eInverseAdd() και eInverseRemove(), οι οποίες περιγράφονται αργότερα.

[9] Στην πραγματικότητα, όλες οι υπαρκτές υλοποιήσεις EList είναι απλές υποκλάσεις μίας πολύ λειτουργικής και αποτελεσματικής βασικής κλάσης υλοποίησης, της EcoreEList.

[10] Στο eInverseAdd(), αντί να χρησιμοποιηθεί απλά η παρεχόμενη ταυτότητα λειτουργίας, πρώτα καλείται το eDerivedStructuralFeatureID(featureID, baseClass). Για απλά μεμονωμένα μοντέλα κληρονόμησης, αυτή η μέθοδος διαθέτει μια προεπιλεγμένη υλοποίηση που παραβλέπει το δεύτερο όρισμα και επιστρέφει την παρεχόμενη ταυτότητα featureID. Για τα μοντέλα που χρησιμοποιούν πολλαπλές κληρονομήσεις, το eDerivedStructuralFeatureID() ενδέχεται να περιέχει μια αντικατάσταση που προσαρμόζει μια ταυτότητα λειτουργίας που είναι σχετική με μια κλάση μίξης (δηλαδή, baseClass) σε μια ταυτότητα λειτουργίας που είναι σχετική με την υπαρκτή παραγόμενη κλάση της χρήσης.

[11] Το EObjectImpl επιπλέον διαθέτει μια μεταβλητή χρήσης eContainerFeatureID είδους ακέραιου αριθμού για την παρακολούθηση των παραπομπών που χρησιμοποιούνται για το eContainer.

[12] Βλ. Replace Enums with Classes.

[13] Για λόγους συμφωνίας με το στυλ προγραμματισμού Java, τα ονόματα των στατικών σταθερών μετατρέπονται σε κεφαλαία γράμματα αν τα μοντελοποιημένα ονόματα λεκτικών μονάδων απαρίθμησης δεν αποτελούνται ήδη από κεφαλαία γράμματα.

[14]  Παρόλο που δεν είναι υποχρεωτικό τα προγράμματά σας να χρησιμοποιούν τις διεπαφές Factory και Package, το EMF ενθαρρύνει τους πελάτες να χρησιμοποιούν τη μέθοδο κατασκευής για να δημιουργούν χρήσεις, παράγοντας προστατευμένες λειτουργίες κατασκευής στις κλάσεις του μοντέλου και όχι απλά καλώντας τη μέθοδο new. Αν το επιθυμείτε ωστόσο, μπορείτε να αλλάξετε με μη αυτόματο τρόπο το είδος πρόσβασης σε δημόσιο στις δημιουργημένες κλάσεις. Οι προτιμήσεις σας δεν θα αντικατασταθούν αν αποφασίσετε αργότερα να επαναδημιουργήσετε τις κλάσεις σας.

[15] Η πρώτη βασική κλάση στο μοντέλο Ecore είναι εκείνη που χρησιμοποιείται ως βασική κλάση υλοποίησης. Στο διάγραμμα UML, το στερεότυπο <<extend>> απαιτείται για να προσδιοριστεί ότι η κλάση Book πρέπει να είναι πρώτη στην αναπαράσταση Ecore.

[16] Αν γνωρίζετε εκ των προτέρων ότι θα χρειαστεί να παράσχετε προσαρμοσμένη υλοποίηση μιας λειτουργίας, ένας καλύτερος τρόπος είναι η μοντελοποίηση του γνωρίσματος ως μεταβλητού, μια ενέργεια που ενημερώνει τη γεννήτρια ότι πρέπει να δημιουργήσει μόνο τη δομή του κύριου τμήματος μεθόδου, την οποία πρόκειται να υλοποιήσετε.

[17] Το EMF περιλαμβάνει ένα γενικό μηχανισμό για την προσθήκη σημειώσεων με πρόσθετες πληροφορίες σε αντικείμενα μεταμοντέλων. Ο μηχανισμός μπορεί επίσης να χρησιμοποιηθεί για την επισύναψη τεκμηρίωσης χρηστών στα στοιχεία του μοντέλου. Όταν ένα μοντέλο δημιουργηθεί με βάση ένα σχήμα XML, το EMF το χρησιμοποιεί για να αποτυπώσει τις λεπτομέρειες σειριοποίησης που δεν μπορούν να εκφραστούν απευθείας με χρήση του Ecore.

[18] Η δεύτερη γραμμή του παραπάνω κώδικα απαιτείται μόνο όταν εκτελείται μεμονωμένα (δηλαδή, όταν κληθεί απευθείας σε ένα JVM, με τα απαιτούμενα αρχεία JAR του EMF στη διαδρομή κλάσεων). Η ίδια εγγραφή γίνεται αυτόματα στο γενικό μητρώο μεθόδων κατασκευής πόρων όταν το EMF εκτελείται στο Eclipse.

[19] Οι υλοποιήσεις των στοχαστικών μεθόδων δημιουργούνται επίσης για κάθε κλάση μοντέλου. Ενεργοποιούν το είδος λειτουργίας και απλά καλούν τις κατάλληλες δημιουργημένες μεθόδους ασφαλούς είδους.

[20] Ανατρέξτε στον ενδείκτη Μη ορισμένο, στην ενότητα Ενδείκτες ελέγχου δημιουργίας για να δείτε τα περιεχόμενα ενός γνωρίσματος ορισμού.

[21] Σκεφτείτε προσεκτικά πριν δηλώσετε μια λειτουργία έτσι ώστε να μην αναλύει αντιπροσώπους. Εσείς μπορεί να μη χρειαστεί να ορίσετε παραπομπές για πολλαπλά έγγραφα, ωστόσο αυτή τη δυνατότητα μπορεί να τη χρειαστεί κάποιος άλλος χρήστης που επιθυμεί να χρησιμοποιήσει το μοντέλο σας. Η δήλωση μιας λειτουργίας για τη μη ανάλυση αντιπροσώπων μοιάζει με τη δήλωση μιας κλάσης Java ως τελικής.