Az Eclipse Modeling Framework (EMF) áttekintése

Utolsó frissítés: 2005. június 16. (hozzáférhetőségi frissítés)

A dokumentum alapszintű áttekintést nyújt az EMF keretrendszerről és annak kódelőállító mintáiról. Az EMF összes szolgáltatásának teljesebb leírását az EMF: Eclipse Modeling Framework, Második kiadás (Addison-Wesley Professional, 2008) című kiadványban vagy maguknak a keretrendszer osztályoknak a Javadoc dokumentációjában találja.

Tartalom

Bevezetés
EMF modell meghatározása
Java megvalósítás előállítása
Az előállított EMF osztályok használata
Haladó témakörök

Bevezetés

Az EMF egy Java keretrendszer és kódelőállító szolgáltatás, mellyel eszközöket és egyéb alkalmazásokat építhet egy strukturált modell alapján. Azoknak, akik objektumorientált modellezést használnak, az EMF segít a modellek gyors átalakításában hatékony, helyes és könnyen személyre szabható Java kóddá. Azoknak, akik nem feltétlenül vannak meggyőződve a formális modellek értékéről, az EMF ugyanezeket az előnyöket kínálja alacsony belépési költség mellett.

Tehát, mit értünk modell alatt? Modellezés kapcsán általában olyan dolgokra gondolunk, mint például osztálydiagramok, együttműködési diagramok, állapotdiagramok stb. Az UML (Unified Modeling Language) általános jelölésmódot határoz meg ezekhez a diagramokhoz. Az alkalmazások teljes modelljét meg lehet adni UML diagramok kombinációjával. A modell használható pusztán dokumentációs célokra, illetve megfelelő eszközök birtokában használható bemenetként, amelyből előállítja az alkalmazás egy részét, vagy egyszerű esetben az egészét.

Tekintve, hogy ez a fajta modellezés jellemzően drága Objektumorientált elemzés és tervezés (OOA/D) eszközöket igényel, megkérdőjelezheti feni kijelentésünket, miszerint az EMF belépési költsége alacsony. Amiért mégis ezt állítjuk az az, hogy az EMF modell csak egy kis részhalmazát igényli az UML-ben modellezhető dolgoknak; konkrétan az osztályok, valamint azok attribútumainak és viszonyainak egyszerű meghatározását, amelyhez nem szükséges egy mindent átölelő grafikus modellezési eszköz.

Míg az EMF XMI-t (XML metaadat-cserét) használ a modell meghatározásának kanonikus formájaként[1] , többféle módon kialakíthatja az ilyen formátumú modellt:

Az első megközelítés a legközvetlenebb, de általában csak XML szakértők képesek erre. A második lehetőség a legkívánatosabb, amennyiben már teljes képességskálával rendelkező modellezési eszközöket használ. A harmadik megközelítés alacsony költségű módot kínál a Java programozóknak arra, hogy mindössze egy alapszintű Java fejlesztői környezet (például az Eclipse Java fejlesztőeszközei) használatával élvezzék az EMF és a hozzá tartozó kódelőállító előnyeit. Az utolsó megközelítés olyan alkalmazások létrehozásához felel meg leginkább, amelynek egy adott XML fájlformátumot kell olvasnia vagy írnia.

Miután meghatározott egy EMF modellt, az eMF előállító létre tudja hozni a Java megvalósítási osztályok megfelelő készletét. Szerkesztheti ezeket az előállított osztályokat metódusok és példányváltozók hozzáadásával, és továbbra is igény szerint végezhet előállítást a modellből: a hozzáadások megmaradnak az újbóli előállítás során. Ha a hozzáadott kód valami olyantól függ, amit módosított a modellben, akkor továbbra is frissítenie kell a kódot, hogy tükrözze ezeket a módosításokat; máskülönben a modell módosítása és az újbóli előállítás semmilyen hatással nem lesz a kódra.

Azon túl, hogy egyszerűen javítja a hatékonyságot, az EMF használatával végzett alkalmazásépítésnek számos egyéb előnye van, például értesítés a modell megváltozásáról, megmaradási támogatás, beleértve az alapértelmezett XMI és a séma alapú XML példányosítást, keretrendszer a modell érvényesítéséhez, valamint egy nagyon hatékony reflektív API az EMF objektumok általános kezeléséhez. De mind közül a legfontosabb, hogy az EMF alapot biztosít az együttműködéshez más EMF alapú eszközökkel és alkalmazásokkal.

Az EMF két alapvető keretrendszerből áll, a törzs keretrendszerből és az EMF.Edit keretrendszerből. A törzs keretrendszer biztosítja az alapvető előállítási és futási támogatást a modell Java megvalósítási osztályainak létrehozásához. Az EMF.Edit a törzs keretrendszerre épül és kiterjeszti azt. Támogatást nyújt az illesztő osztályok előállításához, amelyek lehetővé teszik a modell megjelenítését és parancs alapú (nem visszavonható) szerkesztését, sőt még egy alap munkamodell-szerkesztőt is biztosít. Az alábbi szakaszok bemutatják a törzs EMF keretrendszer fő szolgáltatásait. Az EMF.Edit leírását egy másik dokumentum, az EMF.Edit áttekintése tartalmazza. Az EMF és az EMF.Edit előállító futtatásával kapcsolatos útmutatást itt talál: Ismertető: EMF modell előállítása.

EMF viszonya az OMG MOF szolgáltatással

Azokban, akik ismerik az OMG (Object Management Group) MOF (Meta Object Facility) szolgáltatást, felmerülhet a kérdés, hogy hogyan iszonyul ehhez az EMF. Az EMF valójában a MOF specifikáció megvalósításának indult, de túlnőtt azon a használata és rengeteg eszköz megvalósítása során szerzett tapasztalatok alapján. Az EMF a MOF API törzs részhalmazának kiemelkedően hatékony Java megvalósításaként tekinthető. A félreértések elkerülése végett azonban a MOF-hoz hasonló törzs metamodell az EMF-ben az Ecore.

A MOF 2.0 jelenlegi előterjesztésében a MOF modell hasonló részhalmaza, az EMOF (Essential MOF) is különválasztásra kerül. Kicsi, legingább névadásbeli különbségek vannak az Ecore és az EMOF között, ám az EMF képes az EMOF példányosításainak átlátszó olvasására és írására.

EMF modell meghatározása

Hogy könnyebb legyen az EMF leírása, induljunk ki egy triviális, egyosztályos modellből, mint ez itt:

Egyosztályos modell: Book a következőkkel: title : String és pages : int

A modell egyetlen osztályt tartalmaz (Book), melynek két attribútuma van: title, ami karaktersorozat típusú és pages, ami pedig egész szám.

A modellmeghatározás a maga egyszerűségében számos módon megadható az EMF kódelőállítónak.

UML

Ha rendelkezik az EMF-fel együttműködő modellezési eszközzel[2] , akkor egyszerűen lerajzolhatja az osztálydiagramot a fentiek szerint.

XMI

Alternatív megoldásként közvetlenül is leírhatja a modellt egy XMI dokumentumban, ami valahogy így nézne ki:

  <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 kevésbé kompakt módon. A diagramon található minden osztály és attribútum rendelkezik egy-egy megfelelő osztály- vagy attribútummeghatározással az XMI dokumentumban.

Annotált Java

Azoknak, akik nem rendelkeznek grafikus modellezési eszközzel és nem is akarnak azzal próbálkozni, hogy kézzel írják be a teljes XMI szintaxist, rendelkezésre áll egy harmadik lehetőség a modell leírásához. Mivel az EMF előállító egy kódösszevonó előállító, ha idejében megad részleges Java felületeket (modellinformációkkal annotálva), akkor az előállító fel tudja használni azokat előállítási metaadatokként és össze tudja vonni az előállított kódot a megvalósítás többi részével.

A Book modellosztály így határozható meg Java-ban:

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

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

Ennél a megközelítésnél Java felületek formájában adjuk meg az összes modellinformációt úgy, hogy standard get metódusok[3]  azonosítják az attribútumokat és a hivatkozásokat. A @model címke azonosítja a kódelőállító számára, hogy mely felületek és felületrészek felelnek meg a modell elemeinek s kívánnak kódelőállítást.

Ebben az egyszerű példában minden modellinformáció elérhető a felület Java vizsgálatán keresztül, ezért nincs szükség további modellinformációkra. Általános esetben azonban a @model címkét további részletek követhetik a modellelemmel kapcsolatban. Ha például azt akarnánk, hogy a pages attribútum csak olvasható legyen (vagyis ne kerüljön előállításra set metódus), akkor hozzá kellene adni a következőt az annotációhoz:

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

Mivel csak az alapértelmezettől eltérő információkat kell megadni, az annotációk rövidek és tömörek maradhatnak.

XML séma

Időnként érdemes lehet olyan sémával leírni a modellt, ami megadja, hogy hogy nézzenek ki a példányosítások. Ez például olyan alkalmazás írásakor hasznos, amelynek XML-t kell használnia az integrációhoz egy meglévő alkalmazással vagy a megfeleléshez egy szabványnak. Így lehetne meghatározni egy sémát, ami egyenértékű a példa book modellel:

  <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 némileg különbözik a másik háromtól, főképp mivel az EMF-nek bizonyos korlátozásokat kell alkalmaznia a használt példányosításra, hogy biztosítsa megfelelését a sémának. Ennek eredményeképpen a sémához létrehozott modell kissé eltér az egyéb módokon megadottaktól. A különbségek részletezése túlmutat jelen áttekintés hatókörén.

A dokumentum további részében UML diagramokat használunk világosságuk és tömörségük miatt. A bemutatott modellezési alapelvek mindegyike kifejezhető annotált Java vagy közvetlen XMI használatával, és a legtöbbnek van XML séma megfelelője. Függetlenül attól, hogy hogyan adja meg az információkat, az EMF által előállított kód ugyanaz lesz.

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

A modellben található minden egyes osztályhoz előállításra kerül egy Java felület és a megfelelő megvalósítási osztály. A példában a Book előállított felülete így néz ki:

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

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

Mindegyik előállított felület tartalmaz lekérdező és beállító metódust a megfelelő modellosztály minden egyes attribútumához és hivatkozásához.

A Book felület kiterjeszti az alap EObject felületet. Az EObject a java.lang.Object EMF megfelelője, vagyis minden EMF osztály alapja. Az EObject és a hozzá tartozó megvalósítási osztály, az EObjectImpl (amelyet később mutatunk be) egy viszonylag egyszerű alap osztályt biztosít, amely lehetővé teszi, hogy a Book részt vegyen az EMF értesítésnyújt és megmaradási keretrendszereiben. Mielőtt megnéznénk, hogy az EObject pontosan mit hoz a keverékbe, nézzük tovább, hogy az EMF hogyan állítja elő a Book-ot.

Minden egyes előállított megvalósítási osztály tartalmazza a megfelelő felületen meghatározott lekérdezők és beállítók, valamint az EMF keretrendszer által igényelt néhány egyéb metódus megvalósítását.

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

  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 előállított get metódus optimálisan hatékony. Egyszerűen visszaadja az attribútumot képviselő példányváltozót.

A set metódus, bár kicsit összetettebb, szintén elég hatékony. A pages példányváltozó beállításán túl a set metódusnak az eNotify() metódus hívásával változási értesítéseket kell küldenie mindazoknak a figyelőknek, akik követik az objektumot. Az olyan esetek optimalizálása érdekében, ahol nincsenek figyelők (például egy parancsfájl alkalmazásban) az értesítési objektum (ENotificationImpl) összeállítását és az eNotify() hívását egy eNotificationRequired() hívás védi. Az eNotificationRequired() alapértelmezett megvalósítása egyszerűen csak azt ellenőrzi, hogy csatlakoznak-e figyelők (illesztők) az objektumhoz. Ezért amikor figyelők nélkül használja az EMF objektumokat, akkor az eNotificationRequired() hívás nem más, mint egy hatékony null mutató ellenőrzés, amely a JIT fordító használatakor kerül beemelésre.

Az egyéb attribútumtípusok (például a String típusú title attribútum) előállított elérő mintái némileg eltérnek, de alapvetően ugyanazok, mint amik az oldalak esetén láthatók[4] .

A hivatkozások (különösen a kétirányúak) előállított elérői kicsit összetettebbek és ezek mutatják az EMF előállító igazi értékét.

Egyirányú hivatkozások

Bővítsük ki a példa modellt egy másik osztállyal (Writer), ami kapcsolódik a Book osztályhoz.

Egyirányú hivatkozás: A Book-nak van egy szerzője a Writer osztályból, melynek tartalma egy name : String

A könyv és írója közötti társítás egy egyszerű egyirányú hivatkozás. A hivatkozás (szerep) neve, amellyel eléri a Writer osztályt a Book osztályból, a szerző.

Ha az EMF előállítón keresztül futtatja ezt a modellt, akkor az az új felület, a Writer és megvalósítási osztály, a WriterImpl létrehozásán túl további get és set metódusokat is létrehoz a Book felületen:

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

Mivel a szerző hivatkozás egyirányú, a setAuthor() metódus megvalósítása nagyon hasonlít egy egyszerű adatbeállítóhoz, mint korábban a setPages() esetén:

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

Az egyetlen eltérés, hogy itt objektummutatót állítunk be, nem csak egy egyszerű adatmezőt.

Mivel objektumhivatkozásról van szó, a getAuthor() metódus viszont kicsit összetettebb. Ez azért van így, mert bizonyos típusú hivatkozásoknak - így az author típusának is - foglalkoznia kell azzal a lehetőséggel, hogy a hivatkozott objektum (jelen esetben a Writer) más erőforrásban (dokumentumban) lehet, nem a forrás objektumban (jelen esetben a Book). Mivel az EMF megmaradási keretrendszer késleltetettett betöltési sémát alkalmaz, az objektummutató (jelen esetben az author) bizonyos ideig az objektum proxyja lehet, nem a tényleges hivatkozott objektum[5] . Ennek eredményeképp 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 példányváltozó egyszerű visszaadása helyett először meghívjuk az örökölt keretrendszer metódust, az eIsProxy() metódust annak ellenőrzéséhez, hogy a módszer proxy-e, majd az eResolveProxy() metódust, ha igen. Utóbbi metódus meghívja az EcoreUtil.resolve() metódust, ami egy statikus segédprogram metódus, és a metódus URI-jának használatával próbálja meg betölteni a cél objektumhoz tartozó dokumentumot, s így magát az objektumot. Ha sikeres, akkor visszaadja a feloldott objektumot. Ha azonban a dokumentum betöltése meghiúsul, akkor ismét csak a proxyt adja vissza[6] .

Kétirányú hivatkozások

Most, hogy láttuk, hogyan a proxy feloldás hogyan befolyásolja bizonyos típusú hivatkozások get mintáját, nézzük meg, hogy hogyan változik a set minta, amikor kétirányúvá tesszük a társítást. Módosítsuk az egyirányú author hozzárendelést erre:

Kétirányú hivatkozás: egy szerző 0 vagy több könyvet írhatott, de egy könyvnek csak egy szerzője lehet

A társítás most már kétirányú, melyet a nyílhegy hiánya jelez a társítást ábrázoló vonal Writer felőli végén. A Writer-től a Books eléréséhez használt szerep neve a books.

Ha újra előállítjuk a modellt, akkor a getAuthor() metódus változatlan marad, a setAuthor() azonban így fog kinézni:

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

Mint látható, kétirányú hivatkozás beállításakor (például author) a hivatkozás másik végét is be kell állítani (az eInverseAdd() meghívásával). Valamint el kell távolítani az esetleges korábbi szerző inverzét (az eInverseRemove() meghívásával), mivel modellünkben a szerző hivatkozás egyszeres (vagyis egy könyvnek csak egy szerzője lehet)[7]  ezért a könyv nem szerepelhet több Writer books hivatkozásában is. Végül beállítjuk az author hivatkozást egy másik előállított metódus (basicSetAuthor()) meghívásával, ami í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ó az egyirányú hivatkozás set metódusához, kivéve, hogy ha a msgs argumentum nem null, akkor hozzáadódik az értesítés, ahelyett, hogy közvetlenül aktiválódna[8] . A kétirányú hivatkozás set műveletében szereplő összes előre/hátra hozzáadás/eltávolítás miatt négy (jelen példában három) különböző értesítés kerülhet előállításra. A NotificationChain összegyűjti ezeket az egyedi értesítéseket, így aktiválásuk késleltethető az összes állapotmódosítás végrehajtása utánra. A sorba állított értesítések a msgs.dispatch() meghívásával kerülnek elküldésre, ahogyan az a fenti setAuthor() metódusban látható.

Multiplicitás-sok hivatkozások

Az előző példában feltűnhetett, hogy a books társítás (Writer -> Book) multiplicitás sok (vagyis 0..*). Más szavakkal: egy író több könyvet is írhatott. Az EMF-ben a multiplicitás-sok hivatkozások (vagyis minden olyan hivatkozás, ahol a felső korlát 1-nél nagyobb) kezelése egy alkalmazás programozási felület alkalmazás programozási felület útján zajlik, ezért csak egy get metódus kerül előállításra a felületen:

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

A getBooks() java.util.List helyett EList értéket ad vissza. Valójában ezek majdnem ugyanazok. Az EList a java.util.List EMF alosztálya, ami két további metódust ad az API-hoz. Ezt nem számítva, az ügyfél szempontjából tekinthető normál Java listának. Ha például hozzá akar adni egy könyvet a books társításhoz, akkor egyszerűen hívja meg a következőt:

  aWriter.getBooks().add(aBook);

vagy ha végig akar lépkedni rajtuk, akkor csináljon valami ilyet:

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

Mint látható, az ügyfél szempontjából a multiplicitás-sok hivatkozásokat kezelő API semmi különös. Mivel azonban a books hivatkozás egy kétirányú társítás része, (a Book.author inverze), végig kell csinálnunk az inverz kézfogást is, amit bemutattunk a setAuthor() metódusnál. Ha megnézzük a getBooks() metódus megvalósítását a WriterImpl-ben, akkor láthatjuk a multiplicitás-sok eset kezelését:

  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 (EObjectWithInverseResolvingEList) ad vissza, amely tartalmazza mindazokat az információkat, amelyek szükségesek a fordított irányú kézfogás végrehajtásához a hozzáadás és eltávolítás hívások során. Az EMF valójában 20 különböző, specializált EList megvalósítást biztosít[9]  a multiplicitás-sok jellemzők összes típusának hatékony megvalósításához. Egyirányú (inverzzel nem rendelkező) társításoknál az EObjectResolvingEList kerül felhasználásra. Ha a hivatkozás nem igényel proxyfeloldást, akkor EObjectWithInverseEList vagy EObjectEList kerülne felhasználásra és így tovább.

A példában a books hivatkozás megvalósításához használt lista a LibraryPackage.BOOK__AUTHOR argumentummal kerül létrehozásra (ez egy előállított statikus egész szám konstans, mely a fordított irányú szolgáltatást képviseli). Az add() hívás során kerül felhasználásra az eInverseAdd() meghívásához a Book-ra, hasonlatosan ahhoz, ahogyan az eInverseAdd() meghívásra került a Writer-re a setAuthor() során. Így néz ki az eInverseAdd() a BookImpl osztályban:

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

Először meghívja az eInverseRemove() metódust az esetleges korábbi szerző eltávolításához (amit korábban ismertettünk a setAuthor() metódus bemutatásakor), majd meghívja a basicSetAuthor() metódust a hivatkozás tényleges beállításához. Habár a példában csak egy kétirányú hivatkozás van, az eInverseAdd() olyan switch utasítást használ, amely a Book osztályon elérhető minden kétirányú hivatkozáshoz tartalmaz egy esetet[10] .

Tartalmazási hivatkozások

Vegyünk fel egy új osztályt ( Library), ami a Books tárolójaként fog működni.

Tartalmazási hivatkozás: a Library 0 vagy több könyvet tartalmaz

A tartalmazási hivatkozást a fekete rombusz jelzi a társítás Library felőli végén. A társítás jelzi, hogy a Library 0 vagy több könyvet összesít érték szerint. Az érték szerinti összesítés (tartalmazás) társítások azért különösen fontosak, mert azonosítják egy cél példány szülőjét vagy tulajdonosát, ami a megőrzéskor következtetni enged az objektum fizikai helyére.

A tartalmazás számos hatással van az előállított kódra. Előszöris, mivel a tartalmazott objektum garantáltan ugyanabban az erőforrásban van, mint a tárolója, nincs szükség proxyfeloldásra. Ezért az előállított get generál a LibraryImpl osztályban nem feloldó EList megvalósítási osztályt fog használni:

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

A proxyfeloldás végrehajtásának elhagyásán túl azEObjectContainmentEList nagyon hatékonyan megvalósítja a contains() műveletet (vagyis állandó időben, nem lineáris időben, mint az általános eset). Ez különösen azért fontos, mert az EMF hivatkozáslistákban nem megengedettek a többször szereplő bejegyzések, ezért contains() hívásra is sor kerül az add() műveletek során.

Mivel egy objektumnak csak egy tárolója lehet, azzal, hogy felvesz egy objektumot egy tartalmazási társításba, egyúttal eltávolítja azt abból a tárolóból, amelyben jelenleg van, függetlenül az aktuális társítástól. Például ha felvesz egy Book elemet egy Library könyvlistájára, azzal esetleg eltávolítja azt egy másik Library könyvlistájáról. Ez ugyanolyan, mint bármely más kétirányú társítás, melynek inverze 1-es multiplicitású. Tételezzük fel azonban, hogy a Writer osztály is rendelkezett tartalmazási társítással a Book osztályhoz, ownedBooks névvel. Ekkor ha egy adott könyvpéldány nem szerepel valamely Writer ownedBooks listáján, akkor amikor felvesszük azt a Library books hivatkozásába, először el kell távolítani a Writer-ből.

Ennek hatékony megvalósítása, hogy az EObjectImpl osztálynak van egy EObject típusú példányváltozója (eContainer), amelyben generikusan tartja a tárolót. Ennek eredményképp a tartalmazási hivatkozások implicit módon mindig kétirányúak. A Library eléréséhez a Book-ból valami ilyesmit írhat:

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

Ha ezt el akarja kerülni, akkor módosíthatja a társítást úgy, hogy kifejezetten kétirányú legyen:

Kétirányú tartalmazási hivatkozás: egy Library 0 vagy több könyvet tartalmaz; Library tartalmazza a könyveket

ekkor az EMF-re bízhatja egy szép típusbiztos get metódus előállítását:

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

Az explicit get metódus az eContainer változót használja az EObjectImpl-ből az előállított példányváltozó helyett, ahogyan azt korábban a nem tárolóhivatkozásoknál láttuk (mint például a fenti getAuthor())[11] .

Felsorolás attribútumok

Eddig azt láttuk, hogy az EMF hogyan kezeli az egyszerű attribútumokat és a különböző fajta hivatkozásokat. Másik gyakran használt attribútumtípus a felsorolás. A felsorolás típusú attribútumok a Java típusbiztos felsorolás mintájának használatával vannak megvalósítva[12] .

Ha hozzáadunk egy felsorolás attribútumot (kategóriát) a Book osztályhoz:

Felsorolás attribútum és meghatározása: a Book a rendelkezik egy BookCategory osztályú kategóriával, mely az alábbiak felsorolása: Mystery, ScienceFiction és Biography

és újból előállítjuk a megvalósítási osztályokat, akkor a Book felület most már tartalmazni fog egy lekérdezőt és egy beállítót a kategóriához:

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

Az előállított felületen a kategória metódusai típusbiztos felsorolás osztályt használnak, melynek neve BookCategory. Ez az osztály statikus konstansokat határoz meg a felsorolás értékeihez, valamint további kényelmi metódusokat, például:

  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átható, a felsorolás osztály statikus egész szám konstansokat biztosít a felsorolás értékeihez, valamint statikus konstansokat magukhoz a felsorolás egyke literál objektumaihoz. Az egész szám konstansok neve ugyanaz, mint a modellhez tartozó literál nevek[13] . A literál konstansok neve ugyanaz, a _LITERAL utótaggal kiegészítve.

A konstansok kényelmes hozzáférést biztosítanak a literálokhoz, amikor például beállítja egy könyv kategóriáját:

  book.setCategory(BookCategory.SCIENCE_FICTION_LITERAL);

A BookCategory konstruktor privát, ezért a felsorolás osztálynak csak azok a példányai kerülnek felhasználásra a MYSTERY_LITERAL, SCIENCE_FICTION_LITERAL és BIOGRAPHY_LITERAL statikus konstansokhoz, amelyek ténylegesen elő is fordulnak. Ennek eredményeképp sosincs szükség egyenlőségi összehasonlításokra (vagyis .equals() hívásokra). A literálok mindig megbízhatóan összehasonlíthatók az egyszerűbb és hatékonyabb == operátorral az alábbiak szerint:

  book.getCategory() == BookCategory.MYSTERY_LITERAL

Amikor sok értékkel végez összehasonlítást, akkor még jobb az egész szám értékeket használó switch utasítás:

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

Olyan helyzetekben, amikor csak egy literál név (String) vagy érték (int) érhető el, kényelmi get() metódusok - amelyekkel le lehet kérni a megfelelő literál objektumot - szintén előállításra kerülnek a felsorolás osztályban.

Gyárak és csomagok

A modell felületein és megvalósítási osztályain túl az EMF legalább két további felületet (és megvalósítási osztályt) is előállít még: egy gyárat és egy csomagot.

A gyár, mint neve jelzi, a modellosztályok példányainak létrehozásához használatos, míg a csomag néhány statikus konstanst (például az előállított metódusok által használt összetevőkonstansokat) és kényelmi metódusokat tartalmaz a modell metaadatainak eléréséhez[14] .

Íme a gyár felület a book példához:

  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 biztosít egy-egy gyár metódust (create) a modellben meghatározott minden egyes osztályhoz, egy elérőt a modell csomagjához, valamint egy statikus konstans hivatkozást (eINSTANCE) a gyár egykére.

A LibraryPackage felület kényelmes hozzáférést biztosít a modell összes metaadatához:

  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ét formában érhetők el: egész szám konstansokban és magukban az Ecore metaobjektumokban. Az egész szám konstansok biztosítják a metainformációk átadásának leghatékonyabb módját. Talán feltűnt, hogy az előállított metódusok ezeket a konstansokat használják a megvalósításaikban. Később, amikor az EMF illesztők megvalósítását vizsgáljuk, látni fogja, hogy ezek a konstansok biztosítják a leghatékonyabb módját annak is, hogy az értesítések kezelésekor megállapítsa, mi változott. A gyárhoz hasonlóan az előállított csomag is biztosít statikus konstans hivatkozást az egyke megvalósítására.

Szülőosztályokkal rendelkező osztályok előállítása

Hozzuk létre a Book modellosztály SchoolBook alosztályát az alábbiak szerint:

Egyedülálló öröklés: a SchoolBook kiterjeszti a Book osztályt

Az EMF előállító a várakozásnak megfelelően kezeli az egyedülálló öröklést: az előállított felület kiterjeszti a felső szintű felületet:

  public interface SchoolBook extends Book

és a megvalósítási osztály kiterjeszti a felső szintű megvalósítási osztályt:

  public class SchoolBookImpl extends BookImpl implements SchoolBook

Mint magában a Java nyelvben, a többszörös felületöröklés is támogatott, de egy EMF osztály csak egy megvalósítási alaposztályt terjeszthet ki. Ezért amikor többszörös örökléssel rendelkező modellünk van, akkor azonosítani kell, hogy a többszörös alapok közül melyiket használjuk a megvalósítás alap osztályaként. A többit ezután egyszerűen mixin felületnek tekinti a rendszer, amelyeknek a megvalósításai a származtatott megvalósítási osztályba vannak előállítva.

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

Többszörös öröklés: A SchoolBook kiterjeszti a Book-ot és az Asset-et (az Asset-hez tartozik egy value : float)

Itt a SchoolBook két osztályból van származtatva: Book és Asset. A Book van azonosítva a megvalósítás alap (kiterjesztett) osztályaként[15] . Ha újra előállítjuk a modellt, akkor a SchoolBook felület most már kiterjeszti a két felületet:

  public interface SchoolBook extends Book, Asset

A megvalósítási osztály ugyanúgy néz ki, mint korábban, csak most már tartalmazza a getValue() és a setValue() kevert metódusok megvalósítását:

  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 személyre szabása

Lehetősége van viselkedés (metódusok és példányváltozók) hozzáadására az előállított Java osztályokhoz anélkül, hogy a módosítások elvesztése miatt kellenne aggódnia, ha később úgy dönt, hogy módosítja a modellt és utána újból előállítja: Például adjuk hozzá az isRecommended() metódust a Book osztályhoz. Ehhez egyszerűen vegye fel az új metódus aláírását a Book Java felületre:

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

valamint a megvalósítását a BookImpl osztályba:

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

Az EMF előállító nem törli ki ezt a módosítást, mert az nem egy előállított metódus, amivel kezdene valamit. Az EMF által előállított összes metódus tartalmaz egy Javadoc megjegyzést, amelyben van egy @generated címke; például:

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

A fájlban szereplő olyan metódusok, amelyek nem tartalmazzák ezt a címkét (például az isRecommended()) érintetlenek maradnak az újbóli előállítások alkalmával. Valójában ha módosítani akarjuk egy előállított metódus megvalósítását, akkor ehhez el kell távolítani belőlük a @generated címkét[16] :

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

Most a hiányzó @generated címke miatt a rendszer felhasználói kódnak tekinti a getTitle() metódust; ha újra előállítjuk a modellt, akkor az előállító észleli az ütközést és egyszerűen eldobja a metódus előállított változatát.

Valójában az előállított metódus eldobása előtt az előállító azt nézi meg, hogy van-e másik előállított metódus a fájlban ugyanezzel a névvel, de a Gen karaktersorozattal kiegészítve. Ha talál ilyet, akkor a metódus újonnan előállított változatának eldobása helyett átirányítja oda a kimenetet. Ha például ki akarjuk terjeszteni az előállított getTitle() megvalósítást, akkor a teljes eldobása helyett egyszerűen át kell nevezni, például így:

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

majd felhasználói metódusként felvenni az újradefiniálást, ami azt teszi, amit akarunk:

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

Ha most újbóli előállítást végzünk, akkor az előállító észleli az ütközést a getTitle() felhasználói változatával, de mivel ott van a @generated getTitleGen() metódus is az osztályban, ez átirányítja ide az újonnan előállított megvalósítást, ahelyett, hogy eldobná.

Műveletek az EMF modellekben

Az attribútumokon és a hivatkozásokon túl műveleteket is felvehet a modellosztályokba. Ha így tesz, akkor az EMF előállító előállítja az aláírásukat a felületbe és egy metódusvázat a megvalósítási osztályba. Az EMF nem modellezi a viselkedést, ezért a megvalósítást felhasználó által megírt Java kódként kell biztosítani.

Ezt megteheti úgy, hogy eltávolítja a @generated címkét az előállított megvalósításból (ennek módját lásd fent), és felveszi ide a kódot. Alternatív megoldásként a modellbe is be lehet foglalni a Java kódot. A Rose eszközben egy szövegmezőbe írhatja azt a Műveletmeghatározás párbeszédablak Szemantika lapján. A rendszer a művelet annotációjaként tárolja a kódot az EMF modellben[17] , és előállításkor beteszi a művelet törzsébe.

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

Példányok létrehozása és elérése

Az előállított osztályok használatával az ügyfélprogram az alábbi egyszerű Java utasításokkal képes a Book létrehozására és inicializálására:

  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 - Writer társítás (author) kétirányú, automatikusan inicializálódik az inverz hivatkozás (books) is. Ennek ellenőrzéséhez végig lehet lépkedni a books hivatkozáson az alábbiak szerint:

  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 állít elő:

  Shakespeare books:
    title: King Lear

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

Ha létre akarunk hozni egy mylibrary.xmi nevű dokumentumot, ami tartalmazza a fenti modellt, akkor mindössze annyi a dolgunk, hogy a program elején létrehozunk egy EMF erőforrást, betesszük a book és a writer objektumot az erőforrásba, majd a végén meghívjuk a save() metódust:

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

  // Az alapértelmezett erőforrásgyár regisztrálása -- csak önálló esetén szükséges!
  resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());

  // A modellfájl URI-jának beszerzése.
  URI fileURI = URI.createFileURI(new File("mylibrary.xmi").getAbsolutePath());

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

  // A book és a writer objektum hozzáadása a tartalomhoz.
  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) {}

Az erőforráskészlet (ResourceSet felület) kerül felhasználásra az EMF erőforrás létrehozásához. Az EMF keretrendszer arra használja az erőforráskészletet, hogy kezeli mindazokat az erőforrásokat, amelyek kereszt-dokumentumhivatkozásokat tartalmazhatnak. Egy nyilvántartás (interface Resource.Factory.Registry) használatával a megfelelő típusú erőforrást hozza létre egy adott URI-hoz annak sémája, fájlkiterjesztése és egyéb lehetséges feltételek alapján. Itt úgy regisztráljuk az XMI erőforrás-megvalósítást, hogy az legyen az alapértelmezett ehhez az erőforráskészlethez[18] . A betöltés során az erőforráskészlet kezeli a kereszt-dokumentumhivatkozások igény szerinti betöltését is.

A program futtatása előállítja a mylibrary.xmi fájlt az alábbihoz 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>

A fent mentett mylibrary.xmi betöltéséhez beállítunk egy erőforráskészletet, majd egyszerűen igény szerint beletöltjük az erőforrást az alábbiak szerint:

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

  // Az alapértelmezett erőforrásgyár regisztrálása -- csak önálló esetén szükséges!
  resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());

  // A csomag regisztrálása -- csak önálló esetén szükséges!
  LibraryPackage libraryPackage = LibraryPackage.eINSTANCE;

   // A modellfájl URI-jának beszerzése.
   URI fileURI = URI.createFileURI(new File("mylibrary.xmi").getAbsolutePath());

   // A fájlhoz tartozó erőforrás igény szerinti betöltése.
   Resource resource = resourceSet.getResource(fileURI, true);

   // Az erőforrás tartalmának kinyomtatása a System.out kimenetbe.
   try
   {
     resource.save(System.out, Collections.EMPTY_MAP);
   }
   catch (IOException e) {}

Most is létrehozunk egy erőforráskészletet, és az önálló esetnél regisztrálunk egy alapértelmezett erőforrás-megvalósítást. Azt is biztosítanunk kell, hogy a csomag regisztrálva legyen a csomagnyilvántartásba. Ezt arra használja az erőforrás, hogy beszerezze a megfelelő metaadatokat és gyárat a modellhez, amelyet betölt. Az előállított csomag felület eINSTANCE mezőjének egyszerű elérése elegendő annak biztosításához, hogy az regisztrálva van.

A példa a save() második formáját használja, ami egy OutputStream folyamot vesz, hogy kinyomtassa a példányosítást a konzolra:

Egyszerűen fel lehet osztani egy modellt több dokumentumba, melyek között kereszthivatkozások vannak. Ha külön dokumentumokba akarjuk példányosítani a könyveket és az írókat a fenti mentés példában, akkor mindössze annyi a teendőnk, hogy létrehozunk egy második erőforrást:

  Resource anotherResource = resourceSet.createResource(anotherFileURI);

és ehhez adjuk az írót, nem az elsőhöz:

  resource.getContents().add(writer); // (kicserélve)
  anotherResource.getContents().add(writer);

Ez két erőforrást állít elő, melyek mindegyike egy-egy objektumot tartalmaz, valamint kereszt-dokumentumhivatkozást a másikra.

Vegye figyelembe, hogy a tartalmazási hivatkozás szükségszerűen maga után vonja, hogy a tartalmazott objektum ugyanabban az erőforrásban van, mint a tároló. Tegyük fel például, hogy létrehoztunk egy Library példányt, amelyben benne van a Book a books tartalmazási hivatkozáson keresztül. Ez automatikusan eltávolította a Book-ot az erőforrás tartalmából, ami ilyen értelemben szintén úgy viselkedik, mint egy tartalmazási hivatkozás. Ha ezután hozzáadjuk a Library-t az erőforráshoz, akkor a book implicit módon az erőforráshoz is tartozna, és a részletei újra példányosítva lennének ott.

Ha az XMI-től eltérő formátumban akarja példányosítani az objektumokat, arra is van lehetőség. Saját példányosítási és értelmezési kódot kell biztosítania. Hozza létre a saját erőforrásosztályát (a ResourceImpl alosztályaként), ami megvalósítja az előnyben részesített példányosítási formátumot, majd regisztrálja azt vagy helyileg az erőforráskészlettel, vagy a globális gyár nyilvántartással, amennyiben azt akarja, hogy mindig ez kerüljön felhasználásra a modellel.

EMF objektumok megfigyelése (adaptálása)

Korábban, amikor megnéztük a set metódusokat az előállított EMF osztályokban, láttuk, hogy egy attribútum vagy hivatkozás megváltozásakor mindig küldésre kerülnek értesítések. Például a BookImpl.setPages() metódus tartalmazta az alábbi sort:

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

Minden EObject fenntarthatja a megfigyelők (illesztők) listáját, amelyek értesítést kapnak, valahányszor állapotváltozás történik. A keretrendszer eNotify() metódusa végigléptet ezen a listán és továbbítja az értesítéseket a megfigyelőknek.

Bármely EObject-hez (például: book) csatlakoztatható megfigyelő úgy, hogy hozzáadja azt az eAdapters listához:

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

Leggyakrabban azonban illesztőgyáron kerülnek hozzáadásra az illesztők az EObject-ekhez. A megfigyelő szerepen túl az illesztők inkább arra használatosak a legtöbb esetben, hogy kiterjesztik annak az objektumnak a viselkedését, amelyhez csatlakoznak. Az ügyfél általában úgy csatlakoztat ilyen kiterjesztett viselkedést, hogy megkér egy illesztőgyárat, hogy adaptálja az objektumot a kívánt típusú kiterjesztéssel. Ez jellemzően valahogy így néz ki:

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

Általában a requiredType az illesztő által támogatott valamilyen felület. Az argumentum lehet például a választott illesztő felületéhez tartozó tényleges java.lang.Class. A visszaadott illesztő ezután az alábbi módon adható tovább a kért felületnek:

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

Az illesztőket gyakran ily módon használják az objektum viselkedésének kiterjesztésére továbbszármaztatás nélkül.

Az értesítések kezeléséhez az illesztőben újra kell definiálni az eNotifyChanged() metódust, melyet minden regisztrált illesztőre meghív az eNotify(). A tipikus illesztők azért valósítják meg az eNotifyChanged() metódust, hogy valamilyen műveletet végezzenek néhány vagy akár az összes értesítésen, az értesítések típusa alapján.

Időnként az illesztők egy adott osztályt (például Book) adaptálnak. Ebben az esetben a notifyChanged() metódus valahogy így néz ki:

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

A notification.getFeatureID() hívás megkapja a Book.class argumentumot annak a lehetőségnek a kezeléséhez, hogy az adaptált objektum nem BookImpl példány, hanem egy többszörös öröklésben részt vevő alosztály, ahol nem a Book az elsődleges (első) felület. Ebben az esetben az értesítésben átadott összetevőazonosító a többi osztályhoz képest relatív, ezért be kell állítani, mielőtt átváltanánk a BOOK__ konstansok használatára. Egyszeres örökléses helyzetekben az argumentum figyelmen kívül marad.

Egy másik általános illesztőtípus nincs adott osztályhoz kötve, hanem helyette a reflektív EMF API használatával végzi a funkcióját. Ahelyett, hogy a getFeatureID() metódust hívná meg az értesítésre, inkább a getFeature() metódust hívja, ami visszaadja a tényleges Ecore összetevőt (vagyis azt az objektumot a metamodellben, ami az összetevőt képviseli).

Reflektív API használata

Minden előállított modellosztály kezelhető az EObject felületen meghatározott reflektív API használatával is:

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

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

A reflektív API használatával így állíthatjuk be egy szerző nevét:

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

illetve így kérhetjük le a nevet:

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

Az elért összetevőt a library csomag egyke példányából beszerzett metaadatok azonosítják.

A reflektív API használata valamivel kevésbé hatékony, mint az előállított getName() és setName() metódusok közvetlen hívása[19] , de megnyitja a lehetőséget a modell teljesen általános eléréséhez. Például az EMF.Edit keretrendszer a reflektív metódusok használatával valósítja meg az általános parancsok egész készletét (például AddCommand, RemoveCommand, SetCommand), amelyek bármely modellel használhatók. Részleteket az EMF.Edit áttekintése című rész tartalmaz.

Az eGet() és az eSet() metóduson túlmenően a reflektív API még két kapcsolódó metódust tartalmaz: eIsSet() és eUnset(). Az eIsSet() metódussal megtudhatja, hogy egy attribútum be van-e állítva vagy sem[20] , míg az eUnset() metódussal megszüntetheti a beállítását (vagy alaphelyzetbe állíthatja azt). Például az általános XMI példányosító az eIsSet() használatával állapítja meg, hogy mely attribútumokat kell példányosítani egy erőforrás mentési művelete során.

Haladó témakörök

Előállítást vezérlő kapcsolók

Számos olyan kapcsolót be lehet állítani a modellösszetevőkhöz, amelyek vezérlik az adott összetevő előállított kódjának mintáját. Jellemzően megfelelőek a kapcsolók alapértelmezett beállításai, így nem túl gyakran kell módosítania őket.

Adattípusok

Mint korábban említettük, a modellben meghatározott összes osztály (például Book, Writer) implicit módon az EMF alap osztályából, az EObject osztályból származtatott. A modell által használt osztályok közül viszont nem feltétlenül mind EObject. Például tételezzük fel, hogy hozzá akar adni egy java.util.Date típusú attribútumot a modellhez. Mielőtt ezt megtennénk, meg kell határoznunk egy EMF DataType elemet a külső típus ábrázolásához. Az UML-ben a datatype sztereotípiával rendelkező osztályt használunk erre a célra:

Adattípus meghatározása: a JavaDate adattípus a javaclass java.util.Date osztályba tartozik

Mint látható, az adattípus egyszerűen egy nevesített elem a modellben, ami proxyként működik valamely Java osztályhoz. A jelenlegi Java osztályt azzal a javaclass sztereotípiával rendelkező attribútumként kell megadni, melynek neve a képviselt osztály teljes képzésű neve. Az adattípus meghatározása után most már deklarálhatunk java.util.Date típusú attribútumokat a következőképp:

Attribútum adattípussal attribútumtípusként: A Book-hoz tartozik publicationDate : JavaDate

Az újbóli előállítás után megjelenik a publicationDate attribútum a Book felületen:

  import java.util.Date;

  public interface Book extends EObject
  {
    ...

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

Mint látható, a Date típusú attribútumot is ugyanúgy kezeli a rendszer, mint bármelyik másikat. Valójában minden attribútum, beleértve a String, int stb. típusúakat, rendelkezik adattípussal típusként. A standard Java típusokkal kapcsolatos egyetlen speciális dolog, hogy vonatkozó adattípusuk előre meghatározott az Ecore modellben, így nem kell őket újraefiniálni minden olyan modellben, amely használja őket.

Az adattípus-meghatározásnak még egy hatása van az előállított modellre. Mivel az adattípusok valamilyen önkényes osztályt képviselnek, egy általános példányosító és értelmező (például az alapértelmezett XMI példányosító) sehonnan sem tudhatja, hogy hogyan mentheti az adott típusba tartozó attribútumok állapotát. Végezzen toString() hívást? Ez egy ésszerű alapértelmezés, de az EMF keretrendszer nem akarja megkövetelni ezt, ezért előállít két további metódust a modellben meghatározott minden adattípushoz a gyár megvalósítási osztályban:

  /**
   * @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ódusok egyszerűen meghívják a szülőosztály megvalósítását, amelyek ésszerű, de nem hatékony alapértelmezéseket biztosítanak: a convertToString() egyszerűen toString() hívást végez az instanceValue elemre, míg a createFromString() Java reflekció használatával megpróbál meghívni egy String konstruktort, vagy ha ez nem sikerül, akkor egy statikus valueOf() metódust, amennyiben létezik ilyen. Jellemzően át kell vennie ezeket a metódusokat (a @generated címkék eltávolításával) és le kell cserélnie őket megfelelő egyéni megvalósításokra:

  /**
   * @generated // (eltávolítva)
   */
  public String convertJavaDateToString(EDataType eDataType, Object instanceValue)
  {
    return instanceValue.toString();
  )

Ecore modell

Íme az Ecore modell teljes osztályhierarchiája (az árnyékolt mezők absztrakt osztályok):

Ecore osztályhierarchia

A hierarchia tartalmazza a jelen dokumentumban bemutatott EMF modellelemeket ábrázoló osztályokat: osztályokat (és azok attribútumait, hivatkozásait és műveleteit), adattípusokat, felsorolásokat, csomagokat és gyárakat.

Maga az EMF Ecore megvalósítása az EMF előállítóval kerül előállításra, és mint ilyen, ugyanazzal az egyszerűsített és hatékony megvalósítással rendelkezik, amelyet jelen dokumentum korábbi szakaszaiban bemutattunk.



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

[2] Jelenleg az EMF a Rational Rose eszközből importálást támogatja, de az előállító architektúra könnyen hozzáidomul más modellezési eszközökhöz is.

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

[4] Számos olyan, a felhasználó által megadható beállítás van, amellyel módosítani lehet az előállított mintákat. Ezek közül néhányat részletesen leírunk a dokumentum későbbi szakaszában (lásd: Előállítást vezérlő kapcsolók).

[5] A tartalmazási hivatkozások, melyeket később részletesen bemutatunk (lásd: Tartalmazási hivatkozások), nem ívelhetnek át dokumentumokon. Van egy olyan kapcsoló is, melyet a felhasználók beállíthatnak a hivatkozás metaadataiban annak jelzésére, hogy a feloldást nem kell meghívni, mert a hivatkozás sosem kerül felhasználásra dokumentumközi példahelyzetben (lásd: Előállítást vezérlő kapcsolók). Ezekben az esetekben az előállított get metódus egyszerűen a mutatót adja vissza.

[6] Azoknak az alkalmazásoknak, amelyeknek megszakadt hivatkozásokkal kell foglalkozniuk és ilyeneket kell kezelniük, meg kell hívniuk az eIsProxy() metódust a get metódus által visszaadott objektumra, hogy lássák, hogy feloldódik-e vagy sem (például: book.getAuthor().eIsProxy()).

[7] Ez nyilvánvalóan nem engedi meg, hogy több szerző legyen, de megtartja a példa modell egyszerűségét.

[8] Azért bízzuk meg a basicSet() metódust, mert az eInverseAdd() és az eInverseRemove() metódusnak is szüksége lesz erre (ezeket a metódusokat kicsit később tárgyaljuk).

[9] Valójában az összes konkrét EList megvalósítás egy nagyon működőképes és hatékony alap megvalósítási osztály, az EcoreEList egyszerű alosztálya.

[10] Az eInverseAdd() metódusban a megadott szolgáltatásazonosító egyszerű bekapcsolása helyett először eDerivedStructuralFeatureID(featureID, baseClass) hívás történik. Egyszerű, egyszeres öröklést tartalmazó modelleknél a metódusnak van egy olyan alapértelmezett megvalósítása, ami figyelmen kívül hagyja a második paramétert és visszaadja az átadott featureID értéket. A többszörös öröklést használó modelleknél elképzelhető, hogy az eDerivedStructuralFeatureID() olyan előállított újradefiniálással rendelkezik, ami egy mixin osztályhoz (vagyis baseClass osztályhoz) viszonyított szolgáltatásazonosítót az adott példány konkrét származtatott osztályához viszonyított szolgáltatásazonosítóra állít át.

[11] Az EObjectImpl egy int típusú eContainerFeatureID példányváltozóval is rendelkezik, amely követi, hogy pillanatnyilag melyik hivatkozás használatos az eContainer-hez.

[12] Lásd: Felsorolások lecserélése osztályokra.

[13] Hogy igazodjon a megfelelő Java programozási stílushoz, a statikus konstansok nevét nagybetűssé alakítja a rendszer, amennyiben a modellezett felsorolás literáljainak nevei még nem nagybetűsek.

[14] Bár nem szigorú követelmény, hogy a program a Factory vagy Package felületet használja, az EMF azt ajánlja az ügyfeleknek, hogy a gyárat használják a példányok létrehozásához úgy, hogy védett konstruktorokat hoznak létre a modellosztályokra, megakadályozva ezáltal, hogy egyszerű new hívással jöjjenek létre a példányok. Megteheti azonban, hogy kézileg nyilvánosra állítja a hozzáférést az előállított osztályokban, amennyiben tényleg ezt akarja. A beállítások nem kerülnek felülírásra, ha később úgy dönt, hogy újból előállítja az osztályokat.

[15] Valójában az Ecore modell első alaposztálya a megvalósítás alaposztályaként használt osztály. Az UML diagramon az <<extend>> sztereotípiával kell jelezni, hogy a Book legyen az első az Ecore ábrázolásban.

[16] Ha előre tudja, hogy majd saját, egyéni megvalósítást akar biztosítani valamely szolgáltatásnak, akkor ennek jobb módja, ha változékonyként modellezi az attribútumot, ami arra utasítja az előállítót, hogy először csak váz metódustörzset állítson elő, amelyet utána majd Önnek kell megvalósítania.

[17] Az EMF általános mechanizmust tartalmaz a metamodell objektumainak annotálásához további információkkal. Ezzel a mechanizmussal felhasználói dokumentációt is csatolhat a modell elemeihez, és amikor a modell előállításra kerül az XML sémából, akkor az EMF erre támaszkodik a példányosítás olyan részleteinek beszerzéséhez, amelyek nem fejezhetők ki közvetlenül az Ecore használatával.

[18] A fenti kód második sora csak önálló futtatás esetén szükséges (vagyis amikor közvetlenül hívja meg egy Java virtuális gépen és a szükséges EMF JAR fájlok az osztályútvonalon vannak). Ugyanez a regisztráció automatikusan megtörténik a globális erőforrásgyár nyilvántartásban, amikor az EMF az Eclipse keretrendszeren belül fut.

[19] A reflektív metódusok megvalósításai minden egyes modellosztályhoz is előállításra kerülnek. Ezek bekapcsolják a szolgáltatástípust, és egyszerűen meghívják a megfelelő előállított típusbiztos metódusokat.

[20] Ha kíváncsi, hogy miből áll egy set attribútum, akkor nézze meg az Unsettable kapcsoló leírását az Előállítást vezérlő kapcsolók című részben.

[21] Gondolja meg jól, hogy úgy deklaráljon-e egy szolgáltatást, hogy nem oldja fel a proxykat. Csak az, hogy nem kell dokumentumközi helyzetben használnia a hivatkozást, még nem jelenti azt, hogy valaki más, aki felhasználja ezt a modellt, szintén nem akar ilyet. Ha úgy deklarál egy szolgáltatást, hogy nem oldja fel a proxykat, azzal tulajdonképpen azt deklarálja, hogy a Java osztály végleges.