Az Eclipse Modeling Framework (EMF) áttekintése

Utolsó frissítés: 2004. június 1.

Ez a dokumentum tartalmazza az EMF alapvető áttekintését és annak kódkészítő mintáit. Az Eclipse Modeling Framework (Addison Wesley, 2003) című kiadvány az EMF jellemzőinek, a Javadoc pedig a keretrendszer osztályainak teljesebb leírását tartalmazza.

Bevezetés

Az EMF egy olyan Java keretrendszer és kódkészítő szolgáltatás, mely eszközök és más alkalmazások szerkezeti modell alapján történő felépítésére alkalmas. Az EMF segítséget nyújt ahhoz, hogy a modelleket gyorsan alakíthassák hatékony, hibátlan és könnyedén testre szabható Java-kóddá azok a fejlesztők, akik az objektumorientált modellezés elvét magukévá tették. Azok számára pedig, akik nem feltétlenül értékelik a formális modelleket, az EMF célja, hogy ugyanazokat az előnyöket nyújtsa rendkívül alacsony bekerülési költségek mellett.

Mit értünk tehát modell alatt? Amikor modellezésről beszélünk, általában olyan dolgokra gondolunk, mint például az osztálydiagramok, az együttműködési ábrák, állapotdiagramok, és így tovább. Az UML (Unified Modeling Language) az ilyen típusú diagramok szabványos jelölését határozza meg. UML-diagramok kombinációi segítségével az alkalmazás teljes modellje megadható. Egy ilyen modell tisztán dokumentációra is használható, de - megfelelő eszközök birtokában - alkalmazások részének vagy - egyszerűbb esetekben - egészének előállítására is alkalmas.

Mivel az ilyen típusú modellezés általában költséges objektumorientált elemzési és tervezési eszközöket (OOA/D) igényel, némelyek talán megkérdőjelezik fenti állításunkat, miszerint az EMF alacsony bekerülési költségeket biztosít. Azért állíthatjuk ezt, mert az EMF-modell az UML által modellezhető dolgoknak csak egy kis részhalmazát igényli, nevezetesen az osztályok, valamint azok jellemzőinek és kapcsolatainak egyszerű meghatározásait, amelyekhez szükségtelen egy teljesen kiépített grafikus modellező eszköz.

Bár az EMF az XMI (XML Metadata Interchange) szabványt használja modelldefiníciói kanonikus formátumához[1] , a modellt többféleképpen lehet ilyen formátumúvá alakítani:

Az első megközelítés a legközvetlenebb, azonban ez általában csak az XML-specialistákat vonzza. A második lehetőség akkor a legelőnyösebb, ha Ön már használ teljes kiépítésű modellező eszközöket. A harmadik megközelítés a tiszta Java-programozók számára nyújt lehetőséget, hogy alacsony költségek mellett, alapvető Java fejlesztői környezetben (például az Eclipse Java fejlesztőeszközeit felhasználva) tapasztalhassák meg az EMF és a kódgenerátor előnyeit. Az utolsó megközelítés olyan alkalmazások létrehozásakor alkalmazható a leginkább, melyeknek egy adott XML fájlformátumban kell olvasnia vagy írnia.

Az EMF-modell meghatározását követően az EMF-generátor létrehozza az annak megfelelő Java megvalósítási osztályok készletét. Metódusok és egyedváltozók hozzáadása céljából az előállított osztályokat szerkeszteni lehet, miután a modellt felhasználva az előállítást szükség szerint meg lehet ismételni: a módosítások az újrakészítés során megmaradnak. Amennyiben a hozzáadott kód függ a modell módosításaitól, a kódot ettől függetlenül frissíteni kell, hogy a változások érvényt nyerjenek; ellenkező esetben a kódot a modellen végrehajtott módosítok és az újrakészítés nem befolyásolják.

Az EMF használata alkalmazások összeállításához a termelékenység növelésén túl még számos más előnyt is nyújt: értesítés modellváltozásról, állandósági támogatás (beleértve az alapértelmezett XMI- és sémaalapú XML-sorozatképzést), modellérvényesítési keretrendszer, valamint a rendkívül hatékony, visszaható alkalmazás programozási felület az EMF-objektumok általános kezelésére. A legfontosabb pedig az, hogy az EMF biztosítja a többi EMF-alapú eszközzel és alkalmazással való együttműködés alapjait.

Az EMF két alapvető keretrendszerből áll: a törzskeretrendszerből és az EMF.Edit keretrendszerből. A törzskeretrendszer alapvető előállítási és futási támogatást nyújt ahhoz, hogy a modellből Java megvalósítási osztályokat készíthessen. Az EMF.Edit kiterjeszti a törzskeretrendszert és tovább épít rá az által, hogy támogatja olyan illesztőosztályok előállítását, amelyek lehetővé teszik a modell megjelenítését és parancsalapú (visszavonható) szerkesztését, valamint egy alapvető modellszerkesztőt is. Az alábbi szakaszok az EMF törzskeretrendszer főbb jellemzőinek leírását tartalmazzák. Az EMF.Edit leírását Az EMF.Edit keretrendszer áttekintése című, különálló dokumentum tartalmazza. Az EMF és EMF.Edit generátor futtatásáról az Ismertető: EMF-modell előállítása című dokumentum tartalmaz útmutatást.

Az EMF kapcsolata az OMG MOF szolgáltatáshoz

Akik ismerik az OMG (Objektumkezelő csoport) MOF (Metaobjektum szolgáltatás) szolgáltatást, azok kíváncsiak lehetnek arra, hogy az EMF keretrendszer ehhez hogyan kapcsolódik. Az EMF valójában az MOF-specifikáció megvalósításaként indult, de - sok-sok eszköz EMF keretrendszert használó megvalósítása során szerzett tapasztalataink alapján - továbbfejlődött. Az EMF keretrendszert úgy lehet elképzelni, mint az MOF alkalmazás programozási felület központi részhalmazának egy rendkívül hatékony megvalósítását Java nyelven. A félreértések elkerülése érdekében azonban az EMF központi - az MOF szolgáltatáshoz hasonló - metamodelljére az Ecore kifejezést használjuk.

Az MOF 2.0 jelenlegi ajánlásában elkülönítették az MOF-modell egy hasonló részhalmazát, melynek elnevezése EMOF (Alapvető fontosságú MOF). Kisebb, főként elnevezésbeni különbségek állnak fenn az Ecore és az EMOF között; az EMF azonban egy az egyben tudja olvasni és írni az EMOF sorbafejtéseit.

Az EMF-modell meghatározása

Az alábbi triviális, egyosztályos modellt feltételezve kezdjük az EMF leírását:

Egyosztályos modell

A modell egy "Book" (Könyv) nevű osztályból áll, melynek két attribútuma van: a String-típusú "title" (cím) és az int-típusú "pages" (oldalak).

Bármilyen triviális is ez a modell, a definíció többféleképpen adható meg az EMF kódgenerátor számára.

UML

Ha egy olyan modellező eszközzel dolgozik, amely támogatja az EMF keretrendszert[2] , az osztálydiagramot egyszerűen megrajzolhatja a fenti formában.

XMI

Ennek alternatívájaként a modellt közvetlenül egy XMI-dokumentumban is leírhatjuk, amely körülbelül így festene:

  <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>

Az XMI-dokumentum ugyanazokat az információkat tartalmazza, mint az osztálydiagram, csak valamivel kevésbé tömörebb formában. Az ábrán minden egyes osztály vagy attribútum az XMI-dokumentumban rendelkezik egy megfelelő osztály-, illetve attribútum-definícióval.

Jegyzetekkel ellátott Java

Egy harmadik lehetőség is fennáll azok számára, akik nem rendelkeznek grafikus modellező eszközzel, illetve akiknek az érdeklődését nem kelti fel az XMI-szintaxis kézi bevitelének lehetősége. Mivel az EMF-generátor kódegyesítést végez, lehetőség van arra, hogy részleges (modellinformációs jegyzetekkel ellátott) Java-felületeket adjunk meg, amit a generátor mint előállított metaadatokat használ fel az így nyert kódnak a megvalósítás fennmaradó részével történő egyesítésekor.

A Java nyelvben így határozhatná meg a Book modellosztályt:

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

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

Ezzel a módszerrel a modellinformációkat Java-felületek formájában, szabványos "get" (lekérdező) metódusok[3]  segítségével adjuk meg, hogy azonosítsuk az attribútumokat és hivatkozásokat. A "@model" címke a kódgenerátor számára azonosítja, hogy mely felületek vagy felületrészek felelnek meg a modell elemeinek, mert ezekre elő kell állítani a kódot.

Egyszerű példánkban minden modellinformáció rendelkezésre áll a Java felület belső vizsgálata révén, és így nincs is szükség további modellinformációkra. Általában azonban a "@model" (modell) címkét további, a modell eleméről szóló információkat tartalmazó részletek követhetik. Ha például azt szeretné, hogy a "pages" attribútum írásvédett legyen (vagyis ne készüljön "set" (beállító) metódus), az alábbiakat kellene a jegyzetekhez hozzáfűzni:

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

Mivel kizárólag azokat az információkat kell megadni, amelyek különböznek az alapértelmezéstől, a jegyzetek egyszerűek és tömörek lehetnek.

XML-séma

Bizonyos esetekben szükség lehet arra, hogy a modell leírását olyan séma segítségével adja meg, amely meghatározza az egyedek sorbafejtésének mikéntjét. Ez akkor lehet hasznos, amikor olyan alkalmazást ír, amelyiknek XML nyelvet kell használnia egy már meglévő alkalmazással történő integráláshoz, illetve egy szabvány betartásához. Az egyszerű "Book" modellel egyenértékű sémát az alábbiak szerint lehetne megadni:

  <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>

Ez a megközelítés valamelyest különbözik a másik háromtól, éspedig főként azért, mert az EMF bizonyos megszorításokkal kell, hogy éljen az általa használandó sorbafejtés tekintetében, hogy a sémának megfelelhessen. Ennek az az eredménye, hogy a séma számára létrehozott modell kissé eltér azoktól a modellektől, amelyeket a másik módszerek használatával adott meg. A különbségek részleteinek taglalása meghaladná az áttekintés hatókörét.

A dokumentum hátralévő részében a világos és tömör bemutatás érdekében UML-diagramokat fogunk használni. A szemléltetett modellezési alapelveket jegyzetekkel ellátott Java, illetve közvetlenül XMI segítségével is be tudnánk mutatni, és legtöbbjük megfeleltethető XML-sémának is. Az EMF által előállított kód azonban minden esetben ugyanaz lesz tekintet nélkül az információk megjelenítésének módjára.

Java-megvalósítás előállítása

A modell minden egyes osztályára készül egy Java-felület és egy megvalósítási osztály. Példánkban a "Book" osztályra előállított felület a következőképpen fest:

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

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

Minden egyes előállított felület tartalmaz lekérdező és beállító metódusokat a megfelelő modellosztály összes attribútumára és hivatkozására.

A "Book" felület kiterjeszti az EObject alapszintű felületet. Az EObject a java.lang.Object megfelelője az EMF keretrendszerben, vagyis az összes EMF-osztály alapja. Az EObject és a vonatkozó megvalósítási osztály, az EObjectImpl (melyet később tárgyalunk) egy viszonylagosan könnyűsúlyú alapszintű osztályt biztosítanak, mely lehetővé teszi a "Book" számára, hogy részt vegyen az EMF értesítési és állandósági keretrendszerben. Mielőtt megnéznénk, hogy az EObject pontosan hogyan járul hozzá ez egész rendszerhez, folytassuk annak vizsgálatával, hogy az EMF hogyan állítja elő a "Book" osztályt.

Minden egyes előállított megvalósítási osztály tartalmazza a vonatkozó felületben meghatározott lekérdezők és beállítók megvalósítását, valamint más, az EMF keretrendszer által igényelt metódusokat.

A "BookImpl" osztály többek között tartalmazni fogja a "title" és "pages" elérők megvalósítását. A "pages" attribútum előállított megvalósítása például az alábbi:

  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));
    }

    ...
  }

Az így elkészített "get" metódus optimálisan hatékony, mivel egyszerűen az attribútumot képviselő egyedváltozót adja vissza.

A "set" metódus is eléggé hatékony, bár valamivel összetettebb. Az egyedváltozó oldalainak beállításán túlmenően a "set" metódusnak értesítést is kell küldenie minden olyan megfigyelőnek, mely az eNotify() metódus hívása révén esetleg lehallgatja az objektumot. Ha nincsenek megfigyelők (például parancsfájlként futtatandó alkalmazások esetén), az értesítési objektum felépítését (ENotificationImpl) és az eNotify() meghívását az eNotificationRequired() felé megtett hívás védi. Az eNotificationRequired() alapértelmezett megvalósítása egyszerűen ellenőrzi, hogy az objektumhoz csatlakozik-e megfigyelő (csatoló). Amikor EMF-objektumokat tehát megfigyelők nélkül használ, akkor az eNotificationRequired() meghívása mindössze egy hatékony nullmutató ellenőrzésével ér fel, amely JIT fordítóprogram használata esetén be van építve.

A többi attribútumra előállított elérőminták (mint például a "String"-típusú címattribútum) némileg különböznek, de alapvetően megegyeznek a "pages"[4]  attribútumnál bemutatottakkal.

A hivatkozások előállított elérői (és főként a kétirányúak) egy kicsit komplikáltabbak, ami az EMF-generátor valódi előnyeit kezdi kidomborítani.

Egyirányú hivatkozások

Bővítsük ki példamodellünket a "Writer" (Író) osztállyal, mely a "Book" osztályhoz kapcsolódik:

Egyirányú hivatkozás

A "Book" és "Writer" osztályok közötti társítás ebben a példában egyetlen egyirányú hivatkozás. A "Writer" osztály "Book" osztálytól történő eléréséhez használt hivatkozás (szerepkör) neve "author" (szerző).

Ha betápláljuk ezt a modellt az EMF-generátorba, a program az új "Writer" felület és "WriterImpl" megvalósítási osztály előállításán túlmenően további "get" és "set" metódusokat is készít a "Book" felületen:

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

Mivel az "author" hivatkozása egyirányú, a setAuthor() metódus megvalósítása hasonlít az egyszerű adatbeállítóra, mint például azt korábban a setPages() esetében láttuk:

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

Az egyetlen különbség az, hogy most egy objektummutatót állítunk be szemben egy egyszerű adatmezővel.

Mivel itt objektumhivatkozással van dolgunk, a getAuthor() metódus kissé összetettebb. Ez azért van, mert a "get" metódusnak bizonyos típusú hivatkozásoknál (beleértve az "author"-típust is) azzal az eshetőséggel is számolnia kell, hogy a hivatkozott objektumot (ebben az esetben a "Writer" osztályt) a rendszer a forrásobjektumétól eltérő információforrásban (dokumentumban) tárolja (ebben az esetben a "Book" osztályban). Mivel az EMF megmaradási keretrendszer késleltetett betöltési sémát használ, előfordulhat, hogy az objektummutató (ebben az esetben az "author") a tényleges hivatkozott objektum helyett az objektum proxyja lesz[5] . Ennek eredményeképpen a getAuthor() metódus így néz ki:

  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;
  }

Az "author" egyedváltozó egyszerű visszaadása helyett először az eIsProxy() örökölt keretrendszer metódust kell hívni, hogy meg lehessen győződni arról, hogy a hivatkozás proxy vagy sem, majd - ha proxy - az eResolveProxy() hívásával kell folytatni. Az utóbbi metódus az EcoreUtil.resolve() statikus segédmetódust hívja, mely megpróbálja betölteni a célobjektum dokumentumát és azt követően az objektumot a proxy URI azonosítójának segítségével. Ha ez sikeres, akkor a segédmetódus visszaadja a feloldott objektumot. Ha azonban a dokumentum betöltése nem sikerül, megint csak a proxyt adja vissza[6] .

Kétirányú hivatkozások

Miután megértettük, hogy bizonyos típusú hivatkozásoknál a proxy feloldása hogyan érinti a "get" mintát, megvizsgálhatjuk, hogy kétirányú társítások esetén hogyan változik a "set" minta. Módosítsuk az egyirányú "author" társítását a következőképpen:

Kétirányú hivatkozás

A társítás most kétirányú, amit a nyílhegy hiánya is jelez a társítási vonal "Writer" felé eső végén. A "Books" osztályt a "Writer" osztály felől elérő szerepkör neve "books".

Ha újra előállítja a modellt, a getAuthor() metódus változatlan marad, a setAuthor() viszont így fog festeni:

  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, ...)); // "érintési" értesítés küldése
  }

Mint látható, az olyan kétirányú hivatkozások beállításánál, mint például az "author", a hivatkozás másik végét is be kell állítani (az eInverseAdd() hívása révén). Az előző "author" hivatkozások inverzét is el kell távolítani (az eInverseRemove() hívása segítségével), mivel a modellben az "author" hivatkozás egyedi (azaz a könyvnek csak egy szerzője lehet)[7]  és ezáltal a "book" nem lehet több "Writer" "book" osztályra vonatkozó hivatkozásában. Végül az "author" hivatkozást egy másik előállított metódus (a basicSetAuthor()) hívásával állítjuk elő, mely metódus így néz ki:

  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;
  }

Ez a metódus nagyon hasonlít az egyirányú hivatkozást alkalmazó "set" metódusra azzal a különbséggel, hogy ha a "msgs" (üzenetek) metódus nem üres, akkor az értesítés hozzáadódik a helyett, hogy közvetlenül menne el[8] . Az kétirányú hivatkozást alkalmazó "set" művelet során történő összes előre/hátra hozzáadás/eltávolítás miatt négy (ebben a példában három) különböző értesítés is készülhet. Az egyes értesítések összegyűjtését a NotificationChain végzi, úgyhogy "elsütésüket" el lehet halasztani addig, amíg az összes állapotváltozás meg nem történt. A sorba állított értesítéseket a msgs.dispatch() hívása segítségével lehet elküldeni, mint ahogy az a fenti setAuthor() metódusban is látható.

Sokasági hivatkozások

Talán észrevették, hogy példánkban a "books" társítás (a "Writer" osztálytól a "Book" osztályhoz) sokasági hozzárendelés (vagyis 0..*). Másképpen kifejezve: egy író több könyvet is írhat. A sokasági hivatkozásokat (azaz bármely olyan hivatkozás, melynél a felső határ nagyobb, mint 1) az EMF keretrendszer adatgyűjtő alkalmazás programozási felület segítségével kezeli, és a felületben ezáltal csak egy "get" metódus készül:

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

Vegyük észre, hogy a getBooks() egy EList osztályt eredményez és nem pedig java.util.List osztályt. Valójában ezek majdnem ugyanazok. Az EList a java.util.List osztály olyan EMF alosztálya, amely az alkalmazás programozási felületet két "move" (áthelyezés) metódussal egészíti ki. Ezt a különbséget leszámítva a EList osztályt szabványos Java List osztálynak lehet tekinteni az ügyfél szempontjából. Ha hozzá szeretnénk adni egy könyvet a "books" társításhoz, egyszerűen az alábbit kell meghívni:

  aWriter.getBooks().add(aBook);

Ha ezt ismételgetni szeretné, akkor járjon el a következőképpen:

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

Mint látható, az alkalmazás programozási felület sokasági hivatkozások kezelésére az ügyfél szempontjából egyáltalán nem különleges dolog. Mivel azonban a "books" hivatkozás egy kétirányú társítás része (a Book.author inverze), szükség van arra a bonyolult inverz párbeszédre, melyet a setAuthor() metódusnál láttunk. Ha megnézzük a getBooks() metódus megvalósítását a WriterImpl osztályban, akkor látjuk, hogy a sokasági esetet hogyan kell kezelni:

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

A getBooks() metódus egy speciális megvalósítási osztályt eredményez, nevezetesen az EObjectWithInverseResolvingEList osztályt, mely azon információk használatával épül fel, amelyekre a meghívások hozzáadása és eltávolítása során történő fordított párbeszéd folytatásához szüksége van. Az EMF valójában 20 különböző specializált EList megvalósítást[9]  nyújt, hogy hatékonyan lehessen megvalósítani a különféle sokasági szolgáltatásokat. Egyirányú társításoknál (vagyis amelyeknek nincs inverze) az EObjectResolvingEList metódust kell használni. Ha a hivatkozást nem kell proxy miatt feloldani, az EObjectWithInverseEList vagy EObjectEList metódust kell használni, és így tovább.

Példánkban a "books" hivatkozást megvalósító listát a LibraryPackage.BOOK__AUTHOR argumentum létesíti (mely a fordított szolgáltatást képviselő, előállított statikus "int" állandó). Ez az add() hívás során kerül felhasználásra, mely a "Book" osztályra vonatkozóan meghívja az eInverseAdd() metódust hasonlóan az eInverseAdd() "Writer" osztályra vonatkozó, setAuthor() alatti hívásához. Az eInverseAdd() a BookImpl osztályban a következőképpen fest:

  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:
          ...
      }
    }
    ...
  }

A fenti metódus először az eInverseRemove() metódust hívja meg, hogy eltávolítsa az esetleges korábbi "author" osztályt (mint ahogyan azt a setAuthor() metódusnál előzőleg már leírtuk), majd a basicSetAuthor() metódust hívja a hivatkozás tulajdonképpeni beállításához. Annak ellenére, hogy példánkban csak egy kétirányú hivatkozás van, az eInverseAdd() olyan váltóutasítást használ, amely a "Book" osztály minden rendelkezésre álló kétirányú hivatkozására tartalmaz egy-egy esetet[10] .

Tartalmazó hivatkozások

Vegyen fel egy új, "Library" (Könyvtár) nevű osztályt, mely a "Books" osztály tárolója lesz.

Tartalmazó hivatkozás

A tartalmazó hivatkozást a társítás "Library" oldalán található fekete rombusz jelöli. A társítás jelzi, hogy a "Library" osztály érték szerint 0 vagy több "Book" osztályt köt egybe. Az érték szerinti egybekötési (tartalmazó) társítások különösen fontosak, mivel a célegyed szülőjét vagy tulajdonosát azonosítják, mely tárolás esetén az objektum fizikai helyére utal.

A tartalmazás többféleképpen érinti az előállított kódot. Először is, mivel a tartalmazott objektum garantáltan ugyanabban az erőforrásban található, mint tartalmazója, proxy feloldása nem szükséges. Ennél fogva a LibraryImpl előállított "get" metódusa feloldást nem alkalmazó EList megvalósítási osztályt fog használni:

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

Azon túlmenően, hogy a metódus proxyt nem old fel, az EObjectContainmentEList nagyon hatékonyan valósítja meg a contains() műveletet (vagyis konstans időn belül az általános eset lineáris időszükségletével szemben). Ez különösen fontos, mivel az EMF hivatkozási listáin többszörös bejegyzések nem megengedettek, és így a contains() műveletet az add() műveletek is hívják.

Mivel egy objektumnak csak egy tárolója lehet, az objektum tartalmazó társításhoz történő felvétele egyben azt is jelenti, hogy az objektumot jelenlegi tárolási helyéről el kell távolítani tekintet nélkül a tényleges társításra. Például egy "Book" felvétele egy "Library" könyvlistájára azzal járhat, hogy az objektumot egy másik "Library" könyvlistájáról le kell venni. Ez nem különbözik attól az esettől, amikor egy kétirányú társítás inverzének egyes sokasága van. Tegyük fel azonban, hogy a "Writer" osztálynak a "Book" osztály felé is volt már tartalmazó társítása, melynek elnevezése "ownedBooks" (birtokoltKönyvek). Ekkor, ha egy adott könyv példánya egy bizonyos "Writer" "ownedBooks" listáján található, az objektumnak a "Library" osztály "books" hivatkozásához történő hozzáadása azt jelentené, hogy a könyvet először a "Writer" osztályból el kell távolítani.

A hatékony megvalósítás érdekében az EObjectImpl alapszintű osztálynak van egy EObject típusú egyedváltozója (eContainer), melyet a tároló (tartalmazó) általános tárolására használ. Ennek eredményeképpen a tartalmazó hivatkozások közvetve mindig kétirányúak. A "Library" osztályt a "Book" osztály felől az alábbiak szerint lehet elérni:

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

Ha el akarja kerülni a lefelé irányuló hivatkozást, módosítsa a társítást, hogy kifejezetten kétirányú legyen:

Kétirányú tartalmazó hivatkozás

és készítsen egy szép típusbiztos "get" metódust az EMF keretrendszer segítségével:

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

Vegyük észre, hogy az explicit "get" metódus előállított egyedváltozó helyett az EObjectImpl osztály eContainer változóját használja, ahogyan azt a nem tartalmazó hivatkozásoknál már előzőleg is láttuk (mint például a fenti getAuthor() esetében)[11] .

Felsorolási jellemzők

Eddig azt vizsgáltuk, hogy az EMF hogyan kezeli az egyszerű attribútumokat és a különféle hivatkozásokat. Az attribútumok egy másik gyakran használt válfaja a felsorolási típus. A felsorolás típusú jellemzők megvalósítása a Java típusbiztos "enum" mintája segítségével történik[12] .

Ha felveszünk egy "category" (kategória) nevű felsorolási jellemzőt a "Book" osztályba:

Felsorolási jellemző és meghatározása

és újra előállítjuk a megvalósítási osztályokat, a "Book" felület "category" attribútuma tartalmazni fog egy lekérdezőt és egy beállítót:

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

Az így előállított felületben a "category" metódusok egy "BookCategory" (KönyvKategória) nevű, típusbiztos felsorolási osztályt használnak. Ez az osztály a felsorolás értékeit és más kényelmi metódusait statikus állandókként határozza meg az alábbi módon:

  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);
    }
  }

Mint láttuk, a felsorolási osztály statikus "int" állandókat biztosít a felsorolás értékei és statikus állandókat a felsorolás egyke literál objektumai számára. Az "int konstansok a modell literál neveivel egyező nevet viselnek[13] . A literál állandók ugyanazt a nevet viselik a _LITERAL hozzáfűzésével kiegészítve.

Az állandók kényelmes hozzáférést biztosítanak a literálokhoz például egy könyv kategóriájának beállításakor:

  book.setCategory(BookCategory.SCIENCE_FICTION_LITERAL);

A BookCategory privát konstruktor, és ezáltal a felsorolási osztály létező példányai kizárólag azok lesznek, amelyek a MYSTERY_LITERAL, SCIENCE_FICTION_LITERAL, és BIOGRAPHY_LITERAL statikus állandóknál használatosak. Ennek eredményeképpen egyenlőségi összehasonlításokra (vagyis .equals() hívásokra) soha nincs szükség. A literálokat mindig megbízhatóan össze lehet hasonlítani az egyszerűbb és hatékonyabb == műveleti jel segítségével, például:

  book.getCategory() == BookCategory.MYSTERY_LITERAL

Több értékkel történő összehasonlítás esetén még jobb a kapcsolóutasítás használata "int" értékekkel:

  switch (book.getCategory().value()) {
    case BookCategory.MYSTERY:
      // csináljon valamit ...
      break;
    case BookCategory.SCIENCE_FICTION:
      ...
  }

Azokban az esetekben, amikor csak a literál név (String) vagy érték (int) áll rendelkezésre, a felsorolási osztályban kényelmi "get" metódusok készülnek, melyeket fel lehet használni a megfelelő literál objektum lekérésére.

Gyárak és csomagok

A modellfelületeken és megvalósítási osztályokon túlmenően az EMF még legalább két további felületet (és megvalósítási osztályt) hoz létre: a gyárat és a csomagot.

A gyárat - mint azt a neve is sugallja - a modellosztályok egyedeinek létrehozására lehet használni, míg a csomag statikus konstansokat (például az előállított metódusok által használt szolgáltatási állandókat) és kényelmi metódusokat biztosít a modell metaadatainak eléréséhez[14] .

Az alábbiakban adjuk meg a könyves példa gyári felületét:

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

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

    LibraryPackage getLibraryPackage();
  }

Mint látható, az előállított gyár a modell minden megadott osztálya számára egy gyári metódust (create), a modell csomagja számára egy elérőt, valamint a gyári egyke számára egy állandó hivatkozást (eINSTANCE) biztosít.

A LibraryPackage felület a modell összes metaadataihoz kényelmes elérést biztosít:

  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();

    ...
  }

Mint látható, a metaadatok kétféle formátumban állnak rendelkezésre: mint "int" állandók és maguk az Ecore metaobjektumok. Az "int" állandók jelentik a metainformációk körbeadásának leghatékonyabb módját. Feltűnhetett, hogy az előállított metódusok megvalósításukban ezeket a konstansokat használják. A későbbiekben - amikor megvizsgáljuk az EMF illesztők megvalósítási lehetőségeit - látni fogjuk, hogy az állandók ugyancsak a leghatékonyabb módja annak eldöntése, hogy az értesítések kezelése folyamán mi változott meg. A gyárhoz hasonlóan az előállított csomag is statikus állandót alkalmazó hivatkozást biztosít az egyke megvalósításai számára.

Osztályok előállítása kiemelt osztályokkal

Tételezzük fel, hogy létre akarunk hozni egy "SchoolBook" (IskolaiKönyv) nevű osztályt, mely a modell "Book" osztályának lenne az alosztálya:

Egyedüli öröklés

Az EMF generátor úgy kezeli az egyedüli öröklést, ahogy azt várnánk tőle: az előállított felület kiterjeszti a kiemelt felületet:

  public interface SchoolBook extends Book

és a megvalósítási osztály kiterjeszti a kiemelt megvalósítási osztályt:

  public class SchoolBookImpl extends BookImpl implements SchoolBook

Az EMF a Java nyelvhez hasonlóan támogatja a többszörös felületi öröklést, de minden egyes EMF osztály csak egy megvalósítási alaposztályt tud kiterjeszteni. Ennél fogva, amikor a modellben többszörös öröklés van, meg kell jelölni a megvalósítási alaposztályként hasznosítandó többszörös alaposztályt. A többi osztályt ezek után a rendszer egyszerűen vegyített felületként kezeli úgy, hogy megvalósításukat beolvasztja a származtatott megvalósítási osztályba.

Nézzük meg az alábbi példát:

Többszörös öröklés

A "SchoolBook" alosztályt a következő két osztályból származtatjuk: "Book" és "Asset" (Eszköz). Mint látható, a "Book" osztályt (kiterjesztett) megvalósítási alaposztályként azonosítottuk[15] . Ha a modellt újra előállítjuk, a "SchoolBook" felület két felületre fog kiterjedni:

  public interface SchoolBook extends Book, Asset

A megvalósítási osztály ugyanúgy néz ki, mint korábban, de most tartalmazza a vegyített getValue() és setValue() metódusok megvalósításait is:

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

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

Az előállított megvalósítási osztályok testreszabása

Az előállított Java osztályokat ki lehet egészíteni viselkedésekkel (metódusokkal és egyed változókkal) úgy, hogy amiatt kellene aggódni, hogy ezek a változások elvesznek a modell későbbi módosítása és újrakészítése során. Egészítsük ki például a "Book" osztályt az isRecommended() metódussal. Mindössze annyi a teendő, hogy az új metódus aláírását hozzáadjuk a "Book" Java felülethez:

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

és annak megvalósítását a "BookImpl" osztályhoz:

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

Az EMF generátor nem fogja kitörölni ezt a módosítást, mert - először is - ez nem egy előállított metódus. Az EMF által készített minden egyes metódus tartalmaz egy "@generated" címkével ellátott Javadoc megjegyzést:

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

Az újrakészítés során az EMF érintetlenül hagyja azokat a metódusokat, amelyek nem tartalmazzák ezt a címkét (mint például az isRecommended()). Ha például módosítani szeretnénk egy előállított metódus megvalósítását, ezt a "@generated" címke eltávolításával érhetjük el[16] :

  /**
   * ...
   * @generated
   */
  public String getTitle()
  {
    // saját egyéni megvalósításunk ...
  }

A hiányzó "@generated" címke miatt a getTitle() metódust a rendszer most felhasználói kódként fogja kezelni; a modell újrakészítésénél a generátor felismeri az ellentmondást és egyszerűen eldobja a metódus előállított változatát.

Mielőtt a generátor elvetne egy előállított metódust, először valójában ellenőrzi, hogy a fájlban van-e egy olyan másik előállított metódus, melynek egyezik a neve, de tartalmazza a "Gen" hozzáfűzött címkét is. Ha talál ilyet, akkor a metódus újonnan előállított változatának eldobása helyett átirányítja erre a kimenetet. Ha például ki szeretnénk terjeszteni az előállított getTitle() megvalósítását, ezt a teljes kód törlése helyett egy egyszerű átnevezéssel az alábbiak szerint tehetjük meg:

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

majd pedig újradefiniálhatjuk felhasználói metódusként, mely akaratunk szerint viselkedik:

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

Az újrakészítésnél a generátor megállapítja az ütközést a getTitle() felhasználói változatával, de mivel az osztály tartalmazza a @generated getTitleGen() metódust, nem veti el az újonnan előállított megvalósítást, hanem átirányítja azt ehhez a metódushoz.

Műveletek EMF-modellekben

Az attribútumokon és hivatkozásokon túlmenően a modellosztályokban műveleteket is fel lehet venni. Ebben az esetben az EMF-generátor az aláírást a felületben, a metódus vázát pedig a megvalósítási osztályban állítja elő. Az EMF nem modellezi a viselkedést, így a megvalósítást a felhasználó által írt Java kód formájában kell biztosítani.

Ezt például a fentiekben leírt módon, a "@generated" címkének az előállított kódból történőeltávolításával és a kód helybeni kiegészítésével lehet elérni. Ennek alternatívájaként a Java kódot magában a modellben is meg lehet adni. A Rose eszközben a kódot be lehet írni a Művelet meghatározása párbeszédablak Jelentés lapja szövegmezőjébe. Ezáltal a kód a művelet jegyzeteként kerül tárolásra az EMF-modellben[17] , és az előállítás során így lesz a törzs része.

Az előállított EMF-osztályok használata

Egyedek készítése és elérése

Felhasználva az előállított osztályokat, az ügyfélprogram az alábbi egyszerű Java utasítások segítségével létrehozhatja és inicializálhatja a "Book" osztályt:

  LibraryFactory factory = LibraryFactory.eINSTANCE;

  Book book = factory.createBook();

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

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

Mivel a "Book" és "Writer" osztályok közötti társítás ("author") kétirányú, a rendszer az inverz hivatkozást ("books") automatikusan inicializálja. Erről a "books" hivatkozás alábbiakban bemutatott iterálása révén lehet meggyőződni:

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

A program futtatása az alábbihoz hasonló kimenetet eredményezne:

  Shakespeare books:
    title: King Lear

Erőforrások betöltése és mentése

A fenti modellt tartalmazó, mylibrary.xmi nevű dokumentum létrehozása érdekében pusztán létesíteni kell egy EMF-erőforrást a program elején, hozzá kell adni a "book" és "writer" objektumokat az erőforráshoz, majd meg kell hívni a save() metódust a végén:

  // Erőforráskészlet létrehozása.
  ResourceSet resourceSet = new ResourceSetImpl();

  // A modellfájl URI azonosítójának lekérése.
  URI fileURI = URI.createFileURI(new File("mylibrary.xmi").getAbsolutePath());

  // Erőforrás létrehozása a fájl számára.
  Resource resource = resourceSet.createResource(fileURI);

  // A "book" és "writer" objektumok felvétele a tartalomba.
  resource.getContents().add(book);
  resource.getContents().add(writer);

  // Az erőforrás tartalmának mentése a fájlrendszerbe.
  try
  {
    resource.save(Collections.EMPTY_MAP);
  }
  catch (IOException e) {}

Vegyük észre, hogy a rendszer erőforráskészlet (a ResourceSet felületet) segítségével hozza létre az EMF-erőforrást. Az EMF keretrendszer erőforráskészletet használ az olyan erőforrások kezelésére, melyek dokumentumokon keresztüli hivatkozásokat tartalmazhatnak. A keretrendszer nyilvántartás segítségével (a Resource.Factory.Registry felülettel) hozza létre az adott URI azonosítónak megfelelő típusú erőforrást annak sémája, fájlkiterjesztése vagy más feltételek alapján[18] . Az erőforráskészlet a betöltés folyamán a dokumentumokon keresztüli hivatkozások szükség szerinti betöltését is kezeli.

A program futtatása létrehozza a mylibrary.xmi fájlt az alábbiakhoz hasonló tartalommal:

  <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>

Ha a könyveket és írókat inkább különböző dokumentumokba szeretnénk sorbafejteni, mindössze egy második dokumentumot kellene megnyitnunk:

  Resource anotherResource = resourceSet.createResource(anotherFileURI);

és abba vennünk fel az írót az első erőforrás helyett:

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

Ez két erőforrást hozna létre, melyek mindegyike egy-egy objektumot és egy-egy, a másik dokumentumra mutató, dokumentumokon keresztüli hivatkozást tartalmazna.

Megjegyzendő, hogy a tartalmazó hivatkozás szükségszerűen azt jelenti, hogy a tartalmazott objektum ugyanabban az erőforrásban található, mint annak tárolója. Tételezzük fel például, hogy létrehoztuk a "Library" egy olyan példányát, mely - a "books" tartalmazó hivatkozás révén - tartalmazza a "Book" osztályt. Ez a művelet a "Book" osztályt automatikusan eltávolítaná az erőforrás tartalmából, mely ebben az értelemben ugyancsak tartalmazó hivatkozásként viselkedik. Ha ezek után a "Library" osztályt hozzáadnánk az erőforráshoz, akkor a könyv is közvetetten az erőforráshoz tartozna, és a rendszer elvégezné a könyv részleteinek sorbafejtését az erőforrásban.

Az objektumokat az XMI formátumtól eltérő formátumban is sorba lehet fejteni. Ebben az esetben szükség van a formátum sorbafejtési és értelmezési kódjára. Létre kell hoznia saját erőforrásosztályát (mint a ResourceImpl alosztályát), amely megvalósítja az Ön által előnyben részesített sorbafejtési formátumot, majd vagy helyileg, saját erőforráskészletébe kell azt bejegyeznie, vagy pedig a globális gyárnyilvántartásba, ha azt szeretné, hogy az erőforrásosztály a modellen belül mindig elérhető legyen.

EMF-objektumok megfigyelése (illesztése)

Amikor korábban megvizsgáltuk az előállított EMF-osztályok "set" metódusait, akkor láttuk, hogy egy jellemző vagy hivatkozás változásakor a rendszer mindig küld egy értesítést. A BookImpl.setPages() metódus például magában foglalta az alábbi sort:

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

Minden egyes EObject listát vezethet a megfigyelőkről (melyek illesztőkként is ismertek), melyeket a rendszer állapotváltozás esetén értesít. A keretrendszer eNotify() metódusa ismételten beolvassa ezt a listát és elküldi az értesítéseket a megfigyelőknek.

Megfigyelőt bármely EObject objektumhoz (például: "book") lehet csatlakoztatni az eAdapters listára történő felvétel által:

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

Az illesztőket azonban gyakrabban illesztőgyár használata segítségével veszik fel EObject objektumokhoz. Az illesztőket - megfigyelői szerepkörükön túlmenően - általában a csatlakoztatott objektum viselkedésének kiterjesztésére használják. Az ügyfél általában úgy végzi el a viselkedés kiterjesztését, hogy megkér egy illesztőgyárat az objektum kívánt típusú kiterjesztésével történő illesztésére. Ez tipikusan így néz ki:

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

A "requiredType" (kívántTípus) általában az illesztő által támogatott felületet képviseli. Az argumentum például lehet a kiválasztott illesztő felületének tényleges java.lang.Class osztálya. A visszadott illesztőt ekkor a következőképpen lehet leküldeni a kívánt felülethez:

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

Az illesztőket gyakran használják a fenti módon, mely által egy objektum viselkedését anélkül lehet kiterjeszteni, hogy alosztályt kellene igénybevenni.

Az illesztő értesítését az eNotifyChanged() metódus hatálytalanításával lehet kezelni, melyet az eNotify() hív minden bejegyzett illesztő esetén. Az illesztők jellemzően azért hajtják végre az eNotifyChanged() metódust, hogy az értesítések egy részére vagy egészére az értesítés típusától függően elvégezhessenek valamilyen műveletet.

Néha az illesztőket egy adott osztály (például: "Book") illesztésére készítik. Ebben az esetben a notifyChanged() metódus például így festhet:

  public void notifyChanged(Notification notification)
  {
    Book book = (Book)notification.getNotifier();
    switch (notification.getFeatureID(Book.class))
    {
      case LibraryPackage.BOOK__TITLE:
        // könyv címe megváltozott
        doSomething();
        break;
      caseLibraryPackage.BOOK__CATEGORY:
        // könyv kategóriája megváltozott
        ...
      case ...
    }
  }

A notification.getFeatureID() hívása annak azon eshetőség kezelése érdekében továbbítja a Book.class paramétert, hogy ha az illesztendő objektum nem a "BookImpl" osztály egy példánya, hanem egy többszörösen öröklődő alosztály egyede, ahol a "Book" nem az elsődleges (első) felület. Ebben az esetben az értesítésben átadott szolgáltatási azonosító a másik osztályhoz képest értelmezendő és ezért azt a BOOK__ állandókat használó váltás előtt ki kell igazítani. Az argumentum egyszeres öröklődési helyzetekben figyelmen kívül marad.

Az illesztők egy másik gyakori típusa nem kötődik egy adott osztályhoz, hanem az EMF visszaható alkalmazás programozási felületét használja fel küldetése elvégzéséhez. A getFeatureID() értesítéskori hívása helyett esetleg a getFeature() metódust hívja, mely a tényleges Ecore szolgáltatást adja vissza (vagyis a metamodell ezen szolgáltatását képviselő objektumát).

A visszaható alkalmazás programozási felület használata

Az EObject felületben meghatározott visszaható alkalmazás programozási felület (API) segítségével mód van az összes előállított modellosztály kezelésére:

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

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

A visszaható API segítségével a következőképpen lehet a szerző nevét beállítani:

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

vagy így is meg lehet szerezni a nevet:

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

Érdemes megjegyezni, hogy az elért szolgáltatást a könyvtárcsomag egyke példányától kapott metaadatok azonosítják.

A visszaható API használata valamivel kevésbe hatékony, mint az előállított getName() és setName() metódusok közvetlen meghívása[19] , viszont lehetővé teszi a modellhez való teljesen általános hozzáférést. A visszaható metódusokat használja például az EMF.Edit keretrendszer az általános parancsok teljes készletének megvalósításához (például: AddCommand, RemoveCommand, SetCommand), melyek bármilyen modellhez használhatóak. További részletek Az EMF.Edit keretrendszer áttekintése című dokumentumban találhatók.

Az eGet() és eSet() metódusokon túl a visszaható API két másik kapcsolódó metódust is tartalmaz: az eIsSet() és eUnset() metódusokat. Az eIsSet() metódust annak kiderítésére lehet használni, hogy egy attribútum be van-e kapcsolva vagy sem[20] , az eUnset() metódus segítségével pedig az attribútumot ki lehet kapcsolni (illetve vissza lehet állítani). Az általános XMI sorbafejtő például az eIsSet() metódust használja annak eldöntésére, hogy mely attribútumokat kell sorbafejteni az erőforrás mentési művelete során.

Haladó témakörök

Az előállítás vezérlőkapcsolói

A modell szolgáltatásaival kapcsolatban számos kapcsolót lehet beállítani az erre a szolgáltatásra előállított kódminta szabályozása érdekében. A kapcsolók alapértelmezett beállítás általában a legtöbb célra megfelelnek, így nincs szükség a túl gyakori módosításra.

Adattípusok

Mint ahogy azt korábban is említettük, a modellekben megadott osztályok (például "Book", "Writer") tulajdonképpen az EObject EMF alapszintű osztályból származnak. A modell által használt osztályok közül azonban nem mindegyik szükségszerűen EObject. Tételezzük fel például, hogy modellünkbe fel szeretnénk venni egy java.util.Date típusú attribútumot. Mielőtt ezt megtehetnénk azonban, meg kell határoznunk egy EMF DataType adattípust, mely a külső típust képviseli. Az UML nyelvben erre a célra adattípus sablonnal rendelkező osztályt használunk:

Adattípus meghatározása

Mint látható, az adattípus egyszerűen a modell megnevezett eleme, mely bizonyos Java osztályok proxyjaként működik. A tényleges Java osztály a "javaclass" sablon attribútumaként van megadva, melynek elnevezése az általa képviselt, teljes képzésű osztály. Az így megadott adattípus segítségével a következőképpen deklarálhatjuk a java.util.Date típusú jellemzőket:

Adattípussal mint attribútum típusával megadott attribútum

Az újrakészítésnél a "publicationDate" (kiadásDátuma) attribútum a "Book" felületben jelenik meg:

  import java.util.Date;

  public interface Book extends EObject
  {
    ...

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

Mint látható, a dátum típusú attribútumot a rendszer többé-kevésbé ugyanúgy kezeli, mint a többit. Valójában minden attribútum (beleértve a "String", az "int" és a többi típust) rendelkezik adattípussal. A szabványos Java-típusok egyetlen különlegessége az, hogy a megfelelő adattípusok az Ecore modellben előre meghatározottak, és így nem kell őket minden olyan modellben újra megadni, melyek felhasználják az ilyen típusokat.

Az adattípus meghatározása még az alábbiakban leírt módon is hatással van az előállított modellre. Mivel az adattípusok tetszőleges osztályt képviselhetnek, az általános példányosító vagy értelmező (például az alapértelmezett XMI példányosító) nem tudhatja, hogy a szóban forgó típus attribútumának állapotát hogyan mentse. Hívja a toString() metódust? Ez egy ésszerű alapértelmezés lenne, az EMF keretrendszer azonban ezt nem követeli meg, és így a gyári megvalósítási osztályban két további metódus állít elő a modellben megadott összes adattípusra:

  /**
   * @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);
  }

Alapértelmezésben ezek a metódusuk egyszerűen meghívják a szülőosztály megvalósításait, melyek ésszerű, de nem kellően hatékony megoldást kínálnak: a convertToString() egyszerűen meghívja a toString() metódust instanceValue egyedértéken, a createFromString() azonban Java visszahatás segítségével próbál meghívni egy String konstruktort, illetve - ha ez sikertelen - egy statikus valueOf() metódust, ha az létezik. Az ilyen metódusokat általában érdemes kezelésbe venni (a "@generated" címkék eltávolításával) és a megfelelő egyéni megvalósításra módosítani:

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

Ecore-modell

Az alábbiakban ábrázoltuk az Ecore-modell teljes hierarchiáját (az árnyékolt keretek absztrakt osztályokat jelölnek):

Ecore-osztályok hierarchiája

Mint látható, a modell néhány ebben a dokumentumban is leírt EMF termékeket képviselő osztályokat mutatja be: osztályokat (és azok attribútumait, hivatkozásait és műveleteit), adattípusokat, felsorolásokat, csomagokat és gyárakat.

Az Ecore EMF általi megvalósítása maga is az EMF-generátor segítségével készült, és ezáltal ugyanolyan könnyed és hatékony, mint amilyennek azt az előző szakaszokban leírtuk.



[1] Az EMF metamodell valójában maga is egy EMF-modell, melynek az alapértelmezett példányosított formája az XMI.

[2] Az EMF jelenleg a Rational Rose eszközből történő importálást támogatja, de a generátor felépítése más modellező eszközhöz is könnyedén alkalmazkodik.

[3] Az EMF a JavaBean komponens egyszerű tulajdonságelérő elnevezési mintáinak részhalmazát használja.

[4] Számos, a felhasználó által meghatározható beállítás áll rendelkezésre az előállított minták módosítására. Ezeket a későbbiekben tárgyaljuk (lásd Az előállítás vezérlőkapcsolói című szakaszt alább).

[5] A tartalmazó hivatkozás - melyeket később tárgyalunk (lásd a Tartalmazó hivatkozások című szakaszt) - nem ívelhet át dokumentumokat. A hivatkozás metaadataiban a felhasználó beállíthat egy olyan kapcsolót, mely jelzi, hogy feloldás meghívására nem lesz szükség, mivel a hivatkozás soha nem szerepel dokumentumokon átívelő helyzetekben (lásd Az előállítás vezérlőkapcsolói című szakaszt). Ezekben az esetekben az előállított "get" metódus egyszerűen a mutatót adja vissza.

[6] Azok az alkalmazások, amelyek megszakadt hivatkozásokat kezelnek, illetve azokkal kénytelenek foglalkozni, az eIsProxy() metódust kell hívniuk a "get" metódus által visszaadott objektumon, hogy megállapíthassák annak feloldási állapotát (például book.getAuthor().eIsProxy()).

[7] Többszörös szerző megadása így tisztán nem lesz lehetséges, de ezáltal a példamodell egyszerű marad.

[8] Azért foglalkozunk egyáltalán a basicSet() metódus megbízásával, mert az eInverseAdd() és eInverseRemove() metódusoknak is szükségük lesz rá, melyeket egy kicsit később tárgyalunk.

[9] A konkrét EList megvalósítások valójában egy rendkívül működőképes és hatékony alapszintű megvalósítási osztály, az EcoreEList egyszerű alosztályai.

[10] Az eInverseAdd() metódus - a szolgáltatás azonosítójának egyszerű bekapcsolása helyett - először az eDerivedStructuralFeatureID(featureID, baseClass) metódust hívja. Az egyszerű öröklésimodellek esetén ennek a metódusnak az alapértelmezett megvalósítása figyelmen kívül hagyja a második argumentumot és az átadott featureID szolgáltatási azonosítót adja vissza. A többszörös öröklést alkalmazó modellek esetében az eDerivedStructuralFeatureID() metódus megvalósítása felülbírálási képességgel rendelkezhet, mely kiigazítja a vegyített osztályhoz (vagyis baseClass alapszintű osztályhoz) viszonyított szolgáltatási azonosítót az egyed konkrét származtatott osztályához viszonyított szolgáltatási azonosítóra.

[11] Az EObjectImpl szintén rendelkezik egy "int" típusú eContainerFeatureID egyedváltozóval, hogy nyomon követhesse az eContainer által éppen használatos hivatkozást.

[12] Lásd http://java.sun.com/developer/JDCTechTips/2001/tt0807.html#tip2.

[13] A Java helyes programozási stílusa betartása érdekében a statikus állandó neveit a rendszer nagybetűssé alakítja át, ha a modellezett felsorolás literál nevei még nem nagybetűsek.

[14] A felhasználói program számára nem kötelező a Gyár és Csomag felületek használata, az EMF mégis arra ösztönzi ügyfeleit, hogy az egyedek készítéséhez használják a gyárat a modellosztályokon történő védett konstruktorok előállítása révén, mely megakadályozza a felhasználót abban, hogy a "new" (új) metódus egyszerű meghívásával hozza létre az egyedeket. A felhasználónak természetesen módjában áll az előállított osztályok hozzáférését nyilvánosra módosítani, ha tényleg ezt szeretnék tenni. A felhasználó beállításait a rendszer nem írja felül, ha az később mégis az osztályok újrakészítése mellett dönt.

[15] Az Ecore első alapszintű osztálya valójában a megvalósítási alaposztályként használatos osztály. Az UML-diagramon az <<extend>> (kiterjeszt) sablon szükséges annak jelzéséhez, hogy a "Book" legyen a Ecore megjelenítés első osztálya.

[16] Ha előre tudja, hogy bizonyos szolgáltatásokat egyénileg szeretne megvalósítani, akkor jobb megoldás az attribútumot változékonyként modellezni, amely arra utasítja a generátort, hogy csak a vázát állítsa elő a metódustörzsnek, mely azután a felhasználó általi megvalósításra vár.

[17] Az EMF tartalmaz olyan általános mechanizmusokat, melyek a metamodell objektumok kiegészítő információkkal történő ellátását szolgálják. Ezt a mechanizmust arra is fel lehet használni, hogy a modell elemeihez a felhasználó leírást csatoljon, melyre azután az EMF támaszkodhat a modell XML-sémából történő létrehozásakor, hogy olyan sorbafejtési részletekhez jusson, melyeket az Ecore közvetlen használatával nem lehet kifejezni.

[18] A fenti kód önmagában futtatva (vagyis közvetlenül a Java virtuális gépben indítva úgy, hogy a szükséges EMF JAR fájlok az osztály elérési útján találhatóak) nem lesz sikeres, mivel nem inicializálja az erőforrásgyár nyilvántartását alapértelmezett erőforrásgyárral. Az erőforráskészlet létrehozását követően azonnal be kell szúrni az alábbi sort:

  resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());

Ekkor az erőforráskészlet az alapértelmezett XMI erőforrás megvalósítását fogja felhasználni a sorbafejtéshez. Ugyanezt a bejegyzést a rendszer automatikusan elvégzi a globális erőforrásgyár nyilvántartásában, amikor az EMF az Eclipse alkalmazáson belül fut.

[19] Ugyancsak elkészülnek az összes modellosztály visszaható metódusának megvalósításai. Ezek a szolgáltatás típusától függően működnek, és egyszerűen meghívják a megfelelő előállított típusbiztos metódusokat.

[20] A beállított attribútum jelentéséről bővebb információk Az előállítás vezérlőkapcsolói című szakaszban találhatók.

[21] Jól gondolja meg, mielőtt egy szolgáltatást úgy deklarál, hogy az ne oldjon fel proxykat. Az a tény, hogy Önnek nincs szüksége a hivatkozást dokumentumokon átívelő helyzetekben használnia még nem jelenti azt, hogy a modell felhasználói közül valaki másnak erre nem lesz szüksége. A szolgáltatás ilyen módon való deklarálása (vagyis, hogy ne oldjon fel proxykat) körülbelül olyan, mintha egy Java osztályt véglegesnek deklarálnánk.