Přehled struktury Eclipse Modeling Framework (EMF)

Poslední aktualizace: 1. června 2004

Tento dokument představuje základní přehled EMF a vzorků generátoru kódu. Podrobnější popis všech funkcí EMF naleznete v publikaci Eclipse Modeling Framework (Addison Wesley, 2003), případně v dokumentaci Javadoc, kde jsou popsány přímo jednotlivé třídy struktury.

Úvod

EMF je struktura a prostředek pro generování kódu v jazyce Java pro tvorbu nástrojů a dalších aplikací na základě strukturovaného modelu. Stoupencům objektově orientovaného modelování pomůže EMF rychle vytvořit z modelů účinný, správný a snadno přizpůsobitelný Java kód. Těm z vás, které dosud neokouzlila hodnota formálních modelů, nabízí EMF stejné výhody a velmi nízké vstupní náklady.

Co tedy máme na mysli pod pojmem model? Když mluvíme o modelování, obvykle tím myslíme věci jako diagramy tříd, diagramy spolupráce, stavové diagramy a podobně. Standardní notaci pro tento druh diagramů definuje jazyk UML (Unified Modeling Language). Pomocí kombinace diagramů UML je možné specifikovat celý model aplikace. Tento model může být použit čistě pro dokumentaci nebo za pomoci příslušných nástrojů jako vstup, ze kterého bude vygenerována část či v jednodušších případech i celá aplikace.

Vzhledem k tomu, že tento druh modelování obvykle vyžaduje nákladné nástroje pro objektově orientovanou analýzu a návrh (OOA/D), můžete o našem tvrzení, že EMF nabízí nízké vstupní náklady, pochybovat. Důvodem našeho tvrzení je však fakt, že model EMF vyžaduje jen malou podmnožinu věcí, které můžete modelovat v UML, konkrétně jednoduché definice tříd a jejich atributy a odkazy, pro které není nutný grafický modelovací nástroj nejvyšší třídy.

EMF používá jako základní formu definice modelu[1]  XMI (XML Metadata Interchange) a existuje několik možností, jak lze dostat váš model do této formy:

První přístup je nejpřímější, ale obvykle přitahuje pouze skalní fanoušky XML. Druhá alternativa je nejvhodnější, pokud již používáte nějaké špičkové modelovací nástroje. Třetí přístup nabízí programátorům specializovaným čistě na jazyk Java levný způsob, jak využít přínosy EMF a jeho generátoru kódu jen s pomocí základního vývojového prostředí Java (například Java Development Tools Eclipse). Poslední přístup se nejlépe uplatní při tvorbě aplikace, která musí číst či zapisovat nějaký konkrétní formát souboru XML.

Jakmile specifikujete model EMF, může generátor EMF vytvořit odpovídající sadu implementačních tříd Java. Tyto vygenerované třídy můžete upravovat, přidávat metody a proměnné instancí a podle potřeby je stále znovu z modelu generovat: vše, co doplníte, zůstane během regenerování zachováno. Pokud kód, který přidáte, závisí na něčem, co jste v modelu změnili, musíte ještě aktualizovat samotný kód, aby odrážel tyto změny; v opačném případě nemají změny modelu a regenerování na váš kód žádný vliv.

Kromě toho, že jednoduše zvýší vaši produktivitu, přináší tvorba aplikací pomocí EMF několik dalších výhod, jako je upozornění na změny modelu, podpora trvalosti včetně výchozí serializace XMI a XML na základě schémat, struktura pro ověřování modelu a vysoce efektivní reflektivní API pro generickou manipulaci s objekty EMF. Nejdůležitější však je, že EMF buduje základy pro možnou spolupráci s ostatními aplikacemi a nástroji na bázi EMF.

EMF se skládá ze dvou základních struktur: jádra struktury a EMF.Edit. Jádro struktury zajišťuje základní generování a podporu běhu programů pro tvorbu implementačních tříd Java modelu. EMF.Edit rozšiřuje jádro struktury a dále na něm staví, přidává podporu generování tříd adaptérů, které umožňují prohlížení a (vratné) úpravy modelu na základě příkazů, a dokonce jakýsi základní funkční editor modelů. Následující oddíly popisují hlavní vlastnosti jádra struktury EMF. EMF.Edit je popsána v samostatném dokumentu Přehled struktury EMF.Edit. Pokyny, jak spouštět generátor EMF.Edit a EMF, naleznete ve Výukovém programu: Generování modelu EMF.

Vztah EMF k OMG MOF

Ty z vás, kteří znají OMG (Object Management Group) MOF (Meta Object Facility), by možná zajímalo, v jakém vztahu je k němu EMF. Prostředí EMF vlastně vzniklo jako implementace specifikací MOF, potom se však dále vyvíjelo na základě zkušeností, které jsme získali při implementaci velkého množství nástrojů s jeho pomocí. EMF je možné považovat za vysoce efektivní Java implementaci podmnožiny jádra API MOF. Abychom však předešli zmatkům, je metamodel jádra podobný MOF v EMF pojmenován Ecore.

V současném návrhu MOF 2.0 je vyčleněna podobná podmnožina modelu MOF, nazvaná EMOF - Essential (základní) MOF. Mezi Ecore a EMOF jsou drobné rozdíly, hlavně v pojmenováních; EMF však dokáže nezávisle na kódu číst i zapisovat serializace EMOF.

Definování modelu EMF

Abychom mohli lépe popsat EMF, začněme předpokladem, že máme triviální model s jedinou třídou podobný tomuto:

Model s jednou třídou

Tento model ukazuje jedinou třídu nazvanou Book (Kniha) se dvěma atributy: title (název) typu String (Řetězec) a pages (stránky) typu int (interval).

Naše definice modelu, jakkoli triviální, může být pro generátor kódu EMF zprostředkována celou řadou způsobů.

UML

Pokud máte modelovací nástroj, který umí spolupracovat s EMF[2] , můžete jednoduše nakreslit diagram třídy, jak je zobrazeno výše.

XMI

Případně lze model popsat přímo v dokumentu XMI, což by vypadalo asi takto:

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

Tento dokument XMI obsahuje stejné informace jako diagram třídy, není však tak kompaktní. Každá třída a atribut v diagramu má odpovídající definici třídy nebo atributu v dokumentu XMI.

Anotovaná Java

Pro ty z vás, kdo nemáte grafický modelovací nástroj ani zájem pokoušet se vložit všechnu syntax XML ručně, existuje třetí možnost, jak popsat svůj model. Generátor EMF dokáže slučovat kód, proto když předem poskytnete částečná rozhraní Java (anotovaná pomocí informací z modelu), může je generátor použít jako metadata při generování a sloučit vygenerovaný kód se zbytkem implementace.

Svou modelovou třídu Book (Kniha) v jazyce Java bychom mohli definovat takto:

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

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

S tímto přístupem poskytujeme všechny informace ve formě rozhraní Java se standardními metodami get[3]  pro identifikaci atributů a odkazů. Značka @model se používá, aby generátor kódu poznal, která rozhraní a jejich části odpovídají prvkům modelu, a je tedy nutné je použít při generování kódu.

U našeho jednoduchého příkladu jsou vlastně všechny informace o modelu k dispozici prostřednictvím prozkoumání Java kódu tohoto rozhraní, takže žádné další informace o modelu nejsou třeba. Obecně vzato však mohou za značkou @model následovat další podrobnosti o daném prvku modelu. Pokud bychom například chtěli, aby atribut pages (stránky) byl pouze pro čtení (tj. bez generování metody set), potřebovali bychom přidat následující anotaci:

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

Protože stačí specifikovat informace, které se liší od výchozích, mohou být anotace stručné a jednoduché.

XML Schema

Možná někdy budete chtít popsat nějaký model pomocí schématu, které určuje, jak by měly vypadat serializace instancí. To je možné využít při psaní aplikace, která musí používat XML pro integraci s nějakou stávající aplikací, nebo aby vyhověla nějakému standardu. Takto bychom specifikovali schéma odpovídající našemu jednoduchému modelu book (knihy):

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

Tento přístup se od ostatních tří poněkud liší hlavně proto, že EMF musí zachovat určitá omezení serializací, které nakonec použije, aby zajistilo shodu se schématem. V důsledku toho vypadá model, který je vytvořen pro schéma, poněkud jinak než model specifikovaný některým ze zbývajících způsobů. Podrobnosti týkající se těchto odlišností jsou nad rámec tohoto přehledu.

Ve zbývající části tohoto dokumentu budeme používat diagramy UML, protože jsou jasné a výstižné. Veškeré koncepty modelování, které zde budeme demonstrovat, je možné vyjádřit také pomocí anotovaného jazyka Java nebo přímo v XMI a pro většinu existují ekvivalenty ve schématech XML Schema. Ať již budou tyto informace poskytnuty jakkoli, kód vygenerovaný v EMF bude stejný.

Generování implementace Java

Pro každou třídu v modelu bude vygenerováno rozhraní Java a odpovídající implementační třída. V našem příkladu vypadá rozhraní vygenerované pro Book (Knihu) takto:

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

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

Každé vygenerované rozhraní obsahuje metody getter a setter pro jednotlivé atributy a odkazy příslušné třídy modelu.

Rozhraní Book (Kniha) je rozšířením základního rozhraní EObject. EObject je v EMF ekvivalentem objektu java.lang.Object, to znamená, že se jedná o základ každé třídy EMF. EObject a příslušná implementační třída EObjectImpl (se kterou se blíže seznámíme později) nabízejí poměrně odlehčenou verzi základní třídy, která umožňuje Book (Knize) účast na upozorněních EMF a strukturách trvalosti. Než se začneme zabývat tím, co přesně přináší EObject, podívejme se ještě, jak EMF generuje Book (Knihu).

Každá vygenerovaná implementační třída zahrnuje implementace metod getter a setter definovaných v odpovídajícím rozhraní a dalších metod požadovaných strukturou EMF.

Třída BookImpl bude kromě jiného zahrnovat implementace přístupových mechanismů pro atributy title (název) a pages (stránky). Například atribut pages (stránky) má následující generovanou implementaci:

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

    ...
  }

Generovaná metoda get má optimální účinnost. Jednoduše vrací proměnnou instance představující atribut.

Metoda set, ačkoli poněkud komplikovanější, je také dosti účinná. Kromě nastavení proměnné instance pages (stránky) musí metoda set voláním metody eNotify() také odeslat upozornění na změny všem pozorovatelům, kteří mohou objektu naslouchat. Kvůli optimalizaci případů, kde nejsou žádní pozorovatelé (například v dávkové aplikaci), jsou konstrukce objektu upozornění (ENotificationImpl) a volání metody eNotify() hlídány voláním metody eNotificationRequired(). Výchozí implementace metody eNotificationRequired() jednoduše kontroluje, zda jsou k objektu přiřazeni nějací pozorovatelé (adaptéry). Proto jsou-li objekty EMF použity bez pozorovatelů, rovná se volání metody eNotificationRequired() v podstatě jen kontrole nedefinovaného ukazatele, která je vložena při použití kompilátoru JIT.

Generované vzory přístupových mechanismů pro ostatní typy atributů, jako je atribut title (název) typu String (Řetězec), se v drobnostech liší, ale v zásadě jsou stejné jako pro pages (stránky)[4] .

Generované přístupové mechanismy pro odkazy, zejména obousměrné, jsou o něco komplikovanější a začínají ukazovat skutečnou hodnotu generátoru EMF.

Jednosměrné odkazy

Nyní rozšíříme model z našeho příkladu o novou třídu Writer (Spisovatel), která je přiřazena třídě Book (Kniha):

Jednosměrný odkaz

Přiřazení mezi book (knihou) a writer (spisovatelem) je jednosměrný odkaz. Odkaz (role) použitý pro přístup k objektu Writer (Spisovatel) z objektu Book (Kniha) je nazvaný author (autor).

Pokud na tento model použijeme generátor EMF, vygeneruje nové rozhraní Writer (Spisovatel) a implementační třídu WriterImpl, a navíc také dodatečné metody get a set v rozhraní Book (Kniha):

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

Protože je odkaz author (autor) jednosměrný, vypadá implementace metody setAuthor() velmi podobně jako jednoduchá datová metoda setter, například pro setPages():

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

Jediný rozdíl je, že zde místo jednoduchého datového pole nastavujeme ukazatel na objekt.

Protože však pracujeme s odkazem na objekt, je metoda getAuthor() poněkud komplikovanější. To proto, že se metoda get pro některé typy odkazů včetně typu author musí vypořádat s možností, že objekt odkazu (v tomto případě Writer - Spisovatel) může přetrvat v jiném prostředku (dokumentu) ze zdrojového objektu (v tomto případě Book - Kniha). Protože struktura trvalosti EMF používá schéma s pomalým načítáním, ukazatel na objekt (v tomto případě author - autor) může být v určitém okamžiku zástupcem objektu, nikoli skutečným objektem odkazu[5] . V důsledku toho vypadá metoda getAuthor() takto:

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

Namísto prostého vrácení proměnné instance author (autor) nejprve zavoláme zděděnou metodu struktury eIsProxy(), abychom zjistili, zda je odkaz zástupcem, a pokud je, zavoláme eResolveProxy(). Poslední uvedená metoda zavolá EcoreUtil.resolve(), statickou obslužnou metodu, která se pokusí načíst dokument cílového objektu, a tedy samotný objekt, pomocí URI zástupce. Pokud je úspěšná, vrátí otevřený objekt. Pokud ovšem dokument nedokáže načíst, vrátí znovu jen zástupce[6] .

Obousměrné odkazy

Nyní rozumíme tomu, jak rozpoznání zástupce ovlivňuje způsob řešení vzorku get (získat) pro určité typy odkazů, a můžeme se podívat, jak se mění vzorek set (nastavit), když je přiřazení obousměrné. Změníme tedy své jednosměrné přiřazení na následující:

Obousměrný odkaz

Nyní je přiřazení obousměrné, jak ukazuje nepřítomnost šipky na konci linie přiřazení u objektu Writer (Spisovatel). Název role použitý pro přístup k Books (Knihám) z objektu Writer (Spisovatel) je books (knihy).

Pokud svůj model regenerujeme, metoda getAuthor() se nijak nezmění, zato metoda setAuthor() bude vypadat takto:

  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, ...)); // poslat upozornění na "změnu"
  }

Jak sami vidíte, když nastavujeme obousměrný odkaz jako je author (autor), musí být nastaven i druhý konec odkazu (voláním metody eInverseAdd()). Také musíme odstranit inverzi jakéhokoli předchozího autora (author) (voláním metody eInverseRemove()), protože v našem modelu je odkaz author (autor) singulární (tzn. jedna kniha - book může mít jen jednoho autora - author)[7] , a proto nemůže být tato book (kniha) v odkazu books (knihy) více než jeden objekt Writer (Spisovatel). Nakonec nastavíme odkaz author (autor) voláním další vygenerované metody (basicSetAuthor()), což vypadá následovně:

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

Tato metoda vypadá velice podobně jako metoda set jednosměrného odkazu, s výjimkou toho, že pokud je argument msgs nenulový, bude upozornění přidáno k němu, místo aby bylo spuštěno přímo[8] . Kvůli přidávání a odebírání v jednom a druhém směru mohou být při operaci nastavování obousměrného odkazu vygenerována až čtyři různá upozornění (v tomto konkrétním případě tři). Ke shromáždění všech těchto upozornění se používá metoda NotificationChain, která umožní odložit jejich spuštění, dokud nebudou provedeny všechny stavové změny. Upozornění seřazená ve frontě jsou odesílána voláním metody msgs.dispatch(), jak je vidět v případě výše uvedené metody setAuthor().

Odkazy typu multiplicity-many

Možná jste si všimli, že přiřazení books (knihy) v našem příkladu (od objektu Writer - Spisovatel k objektu Book - Kniha) je typu multiplicity-many (tzn. 0..*). Jinými slovy jeden spisovatel mohl napsat mnoho knih. Odkazy typu multiplicity-many (tedy veškeré odkazy, kde je horní mez vyšší než 1) jsou v EMF manipulovány pomocí API kolekcí, aby byla rozhraním vygenerována pouze jedna metoda get:

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

Všimněte si, že metoda getBooks() nevrací java.util.List, ale EList. Vlastně jsou téměř stejné. EList je podtřída java.util.List v rámci EMF, která přidává do API dvě metody move. Kromě toho jej z hlediska klienta můžete považovat za standardní seznam Java. Chcete-li například přidat knihu k přiřazení books (knihy), můžete jednoduše zavolat:

  aWriter.getBooks().add(aBook);

nebo chcete-li nad nimi provádět iterace, můžete udělat přibližně toto:

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

Jak vidíte, z pohledu klienta není API pro manipulaci s odkazy typu multiplicity-many nic zvláštního. Protože je však odkaz books (knihy) součástí obousměrného přiřazení (je to inverze Book.author), musíme ještě provést všechny komunikace a výměny potvrzení kolem, které jsme si ukazovali u metody setAuthor(). Podíváme-li se na implementaci metody getBooks() ve WriterImpl, vidíme, jak se ošetřuje případ typu multiplicity-many:

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

Metoda getBooks() vrací speciální implementační třídu EObjectWithInverseResolvingEList, která je vytvořena se všemi informacemi, potřebnými pro zpětné navázání komunikace výměnou potvrzení během volání metod add a remove. EMF ve skutečnosti nabízí 20 různých specializovaných implementací EList[9]  pro efektivní implementaci všech typů vlastností typu multiplicity-many. Pro jednosměrná přiřazení (tedy bez inverze) používáme EObjectResolvingEList. Pokud odkaz nepotřebuje rozpoznání zástupců, používáme EObjectWithInverseEList nebo EObjectEList a tak dále.

Takže například seznam použitý pro implementaci odkazu books (knihy) je vytvořen s argumentem LibraryPackage.BOOK__AUTHOR (generovaná statická konstanta typu interval představující vlastnost inverze). Ten bude použit během volání metody add() k volání metody eInverseAdd() nad na Book (Knihu), podobně jako byla metoda eInverseAdd() volaná na objekt Writer (Spisovatele) během metody setAuthor(). A takto vypadá eInverseAdd() ve třídě BookImpl:

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

Nejprve volá metodu eInverseRemove(), aby odebrala jakýkoli předchozí objekt author (autor) (jak jsme popsali dříve, když jsme blíže zkoumali metodu setAuthor()), a potom volá metodu basicSetAuthor(), aby skutečně nastavila příslušný odkaz. Ačkoli právě náš příklad má pouze jeden obousměrný odkaz, používá eInverseAdd() příkaz přepnutí switch, který zahrnuje jeden případ pro každý obousměrný odkaz dostupný nad třídou Book (Kniha)[10] .

Odkazy obsažení

Přidejme novou třídu Library (Knihovna), která bude fungovat jako pořadač pro objekty Book (Knihy).

Odkaz obsažení

Odkaz obsažení je označen černým diamantem na konci Library (Knihovny) přiřazení. Pokud to shrneme, toto přiřazení značí, že Library (Knihovna) agreguje podle hodnoty 0 či více objektů Book (Kniha). Přiřazení agregace podle hodnoty (obsažení) jsou zvlášť důležitá, protože identifikují nadřazený objekt nebo vlastníka cílové instance, který udává fyzické umístění objektu, pokud přetrvá.

Obsažení ovlivňuje generovaný kód několika způsoby. Protože je u obsaženého objektu zaručeno, že se nachází ve stejném prostředku jako příslušný pořadač, není nutné rozpoznání zástupců. Proto použije generovaná metoda get v LibraryImpl implementační třídu EList bez rozpoznávání:

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

Kromě toho, že neprovádí rozpoznání zástupců, implementuje metoda EObjectContainmentEList také velice efektivně operaci contains() (tj. v konstantním čase, zatímco obecně je to v lineárním čase). To je důležité zejména proto, že v seznamech odkazů EMF nejsou povoleny duplicitní záznamy, takže je operace contains() volána i v průběhu operace add().

Protože objekt může mít pouze jeden pořadač, znamená přidání objektu do přiřazení obsažení zároveň jeho odebrání z jakéhokoli pořadače, v němž se právě nachází, nezávisle na skutečném přiřazení. Například přidání Book (Knihy) do seznamu knih Library (Knihovny) může zároveň znamenat její odstranění ze seznamu knih jiné Library (Knihovny). To se nijak neliší od ostatních obousměrných přiřazení, kde má inverze multiplicitu 1. Předpokládejme však, že třída Writer (Spisovatel) má také přiřazení obsažení na Book (Knihu), nazvanou ownedBooks (vlastní_knihy). Pokud je potom daná instance book (knihy) v seznamu ownedBooks (vlastních knih) nějakého objektu Writer (Spisovatel) a přidáváme ji do odkazu books (knihy) nějaké Library (Knihovny), musela by se nejprve odebrat od objektu Writer (Spisovatel).

Pro efektivní implementaci těchto věcí má základní třída EObjectImpl proměnnou instance (eContainer) typu EObject, kterou používá ke generickému uložení pořadače. V důsledku toho jsou odkazy obsažení vždy implicitně obousměrné. Pro přístup z Book (Knihy) k Library (Knihovně) můžete napsat asi následující kód:

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

Pokud se chcete vyhnout přetypování dolů, můžete místo toho změnit přiřazení na explicitně obousměrné:

Obousměrný odkaz obsažení

a nechat EMF vygenerovat hezkou metodu typesafe get:

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

Všimněte si, že explicitní metoda get používá proměnnou eContainer z EObjectImpl místo generované proměnné instance, jakou jsme viděli dříve u nepořadačových (non-container) odkazů (jako je výše uvedený getAuthor())[11] .

Výčtové atributy

Zatím jsme se zabývali tím, jak EMF ošetřuje jednoduché atributy a různé typy odkazů. Dalším obecně používaným typem atributu je výčet. Atributy výčtového typu jsou implementované pomocí vzoru Java typesafe enum [12] .

Pokud ke třídě Book (Kniha) přidáme výčtový atribut category (kategorie):

Výčtový atribut a definice

a regenerujeme implementační třídy, bude nyní rozhraní Book (Kniha) zahrnovat metody getter a setter pro category (kategorii):

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

V generovaném rozhraní používají metody category (kategorie) výčtovou třídu typesafe nazvanou BookCategory (Kategorie_knih). Tato třída definuje statické konstanty pro hodnoty výčtu a další nadstavbové metody asi takto:

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

Jak je vidět, výčtová třída poskytuje statické konstanty typu interval (int) pro hodnoty výčtů a také statické konstanty pro samostatné literálové objekty výčtu. Konstanty typu interval (int) mají stejné názvy jako literálové názvy modelu[13] . Literálové konstanty mají stejné názvy s přidaným _LITERAL.

Konstanty zajišťují pohodlný přístup k literálům, například při nastavení category (kategorie) objektu book (knihy):

  book.setCategory(BookCategory.SCIENCE_FICTION_LITERAL);

Konstruktor BookCategory (knižní kategorie) je soukromý, a proto jediné instance výčtové třídy, které kdy budou existovat, jsou použité pro statické MYSTERY_LITERAL (detektivky), SCIENCE_FICTION_LITERAL (sci-fi) a BIOGRAPHY_LITERAL (životopisy). V důsledku toho nejsou nikdy třeba porovnání rovnosti (tj. volání .equals()). Literály je možné vždy spolehlivě porovnat pomocí jednoduššího a efektivnějšího operátoru == asi takto:

  book.getCategory() == BookCategory.MYSTERY_LITERAL

Pro porovnání s velkým počtem hodnot je ještě lepší příkaz switch používající hodnoty typu interval (int):

  switch (book.getCategory().value()) {
    case BookCategory.MYSTERY:
      // něco udělat ...
      break;
    case BookCategory.SCIENCE_FICTION:
      ...
  }

V situacích, kde je k dispozici pouze literálový název (Řetězec) nebo hodnota (interval), jsou ve výčtové třídě generovány také nadstavbové metody get(), které lze použít k načtení odpovídajícího literálového objektu.

Továrny a balíčky

Kromě rozhraní modelu a implementačních tříd generuje EMF minimálně dvě další rozhraní (a implementační třídy): továrnu a balíček.

Továrna, jak název napovídá, se používá k vytváření instancí tříd vašich modelů a balíček zajišťuje různé statické konstanty (například konstanty vlastností používané generovanými metodami) a nadstavbové metody pro přístup k metadatům vašeho modelu[14] .

Zde je rozhraní továrny pro příklad s books (knihami):

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

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

    LibraryPackage getLibraryPackage();
  }

Jak je vidět, generovaná továrna zajišťuje tovární metodu (create) pro každou třídu definovanou v modelu, přístupový mechanizmus pro balíček vašeho modelu a statický konstantní odkaz (tj. eINSTANCE) na továrního jedináčka.

Rozhraní LibraryPackage zajišťuje praktický přístup ke všem metadatům našeho modelu:

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

    ...
  }

Jak vidíte, jsou metadata k dispozici ve dvou formách: konstanty typu interval (int) a samotné metaobjekty Ecore. Konstanty typu interval (int) nabízejí nejefektivnější způsob předávání metainformací. Možná jste si všimli, že generované metody používají tyto konstanty ve svých implementacích. Později, až se budeme věnovat tomu, jak je možné implementovat adaptéry EMF, uvidíte, že konstanty nabízejí také nejefektivnější způsob pro určení, co se změnilo při obsluze upozornění. Stejně jako továrna i rozhraní generovaného balíčku zajišťuje statický konstantní odkaz na svou jedinečnou implementaci.

Generování tříd se supertřídami

Řekněme, že chceme vytvořit podtřídu SchoolBook (Učebnice) naší modelové třídy Book (Kniha), a to asi takto:

Jednoduchá dědičnost

Generátor EMF ošetří jednoduchou dědičnost podle očekávání: generované rozhraní je rozšířením superrozhraní:

  public interface SchoolBook extends Book

a implementační třída je rozšířením superimplementační třídy:

  public class SchoolBookImpl extends BookImpl implements SchoolBook

Stejně jako v samotném prostředí Java je podporována vícenásobná dědičnost rozhraní, ale každá třída EMF může rozšiřovat pouze jednu základní implementační třídu. Proto, když máme model s vícenásobnou dědičností, potřebujeme určit, který z několika základů se má použít jako základní třída implementace. S ostatními pak bude nakládáno jednoduše jako s přidanými rozhraními, jejichž implementace se generují do odvozené implementační třídy.

Podívejte se na následující příklad:

Vícenásobná dědičnost

Zde máme SchoolBook (Učebnici), která se odvozuje ze dvou tříd: Book (Kniha) a Asset (Aktivum). Jak je vidět, určili jsme jako základní (rozšířenou) třídu implementace Book (Knihu)[15] . Pokud teď model regenerujeme, bude rozhraní SchoolBook (Učebnice) rozšiřovat tato dvě rozhraní:

  public interface SchoolBook extends Book, Asset

Implementační třída vypadá stejně jako předtím, jen nyní zahrnuje implementace přidaných metod getValue() a setValue():

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

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

    ...
  }

Přizpůsobení generovaných implementačních tříd

Ke generovaným třídám Java můžete přidávat chování (metody a proměnné instancí), aniž byste se museli bát, že o své změny přijdete, jestliže se později rozhodnete model upravit a poté regenerovat. Přidejme například k třídě Book (Kniha) metodu isRecommended(). Stačí, abyste jednoduše přidali podpis nové metody do Java rozhraní Book (Kniha):

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

a její implementaci do třídy BookImpl:

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

Generátor EMF tuto změnu nesmaže už proto, že nejde o generovanou metodu. Každá metoda generovaná EMF zahrnuje komentář dokumentace Javadoc, který obsahuje značku @generated, takto:

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

Všechny metody v souboru, které tuto značku neobsahují (jako isRecommended()), zůstanou při regenerování nedotčené. Kdybychom chtěli změnit implementaci generované metody, můžeme to vlastně provést tak, že z ní odstraníme značku @generated[16] :

  /**
   * ...
   * @generated
   */
  public String getTitle()
  {
    // naše vlastní implementace ...
  }

Nyní, protože postrádá značku @generated, je metoda getTitle() považována za kód vytvořený uživatelem; pokud model regenerujeme, generátor zjistí kolizi a generovanou verzi metody jednoduše vyřadí.

Ve skutečnosti generátor před vyřazením generované metody nejprve zkontroluje, zda se v souboru nachází jiná generovaná metoda se stejným názvem, ale s připojeným Gen. Pokud takovou najde, pak místo vyřazení nově generované verze metody přesměruje výstup sem. Pokud například chceme generovanou implementaci metody getTitle() místo naprostého vyřazení rozšířit, můžeme to udělat jednoduše jejím přejmenováním asi takto:

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

a následným přidáním našeho potlačení jako uživatelské metody, která dělá, co chceme:

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

Pokud nyní regenerujeme, zjistí generátor kolizi s naší uživatelskou verzí metody getTitle(), ale protože máme v dané třídě zároveň metodu @generated getTitleGen(), nově generovaná implementace se do ní přesměruje, místo aby byla vyřazena.

Operace v modelech EMF

Kromě atributů a referencí můžete do tříd svého modelu přidávat také operace. Pokud to uděláte, vygeneruje generátor EMF jejich podpis do rozhraní a kostru metody do implementační třídy. EMF nemodeluje chování, takže tato implementace musí být zajištěna uživatelem psaným kódem Java.

To je možné provést odstraněním značky @generated z generované implementace, jak je popsáno výše, a doplněním kódu přímo na dané místo. Případně může být kód Java obsažen přímo v modelu. V Rose jej můžete zadat do textového pole na kartě Sémantika v dialogovém okně Specifikace operací. Tento kód pak bude uložen v modelu EMF jako anotace operace[17]  a bude generován do těla modelu.

Použití generovaných tříd EMF

Tvorba a přístup k instancím

Pomocí generovaných tříd může program klienta vytvořit a inicializovat Book (Knihu) s následujícími jednoduchými příkazy jazyka Java:

  LibraryFactory factory = LibraryFactory.eINSTANCE;

  Book book = factory.createBook();

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

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

Protože přiřazení author (autor) mezi Book (Knihou) a Writer (Spisovatelem) je obousměrné, automaticky se inicializuje inverzní odkaz books (knihy). Můžeme to ověřit iterací nad odkazem books (knihy) asi takto:

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

Provedení tohoto programu by vyprodukovalo přibližně následující výstup:

  Shakespeare books:
    title: King Lear

Načítání a ukládání prostředků

Vše, co potřebujeme, abychom vytvořili dokument nazvaný mylibrary.xmi obsahující výše uvedený model, je vytvořit na začátku programu prostředek EMF, vložit do tohoto prostředku objekty book (knihu) a writer (spisovatele) a na konci zavolat metodu save():

  // Vytvořit množinu prostředků.
  ResourceSet resourceSet = new ResourceSetImpl();

  // Získat URI souboru modelu.
  URI fileURI = URI.createFileURI(new File("mylibrary.xmi").getAbsolutePath());

  // Vytvořit prostředek pro tento soubor.
  Resource resource = resourceSet.createResource(fileURI);

  // Přidat do obsahu objekty book a writer.
  resource.getContents().add(book);
  resource.getContents().add(writer);

  // Uložit obsah prostředku do systému souborů.
  try
  {
    resource.save(Collections.EMPTY_MAP);
  }
  catch (IOException e) {}

Všimněte si, že je k vytvoření prostředku EMF použita množina prostředků (rozhraní ResourceSet). Množina prostředků je strukturou EMF používána ke správě prostředků, které mohou mít vzájemné odkazy mezi dokumenty. Pomocí registru (rozhraní Resource.Factory.Registry) vytvoří správný typ prostředku pro daný URI na základě jeho schématu, přípony souboru, či dalších možných kritérií[18] . Během načítání také spravuje načítání vzájemných odkazů mezi dokumenty na vyžádání.

Provedení tohoto programu vyprodukuje soubor mylibrary.xmi s přibližně následujícím obsahem:

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

Pokud bychom místo toho chtěli books (knihy) a writers (spisovatele) serializovat do různých dokumentů, stačilo by jen otevřít druhý dokument:

  Resource anotherResource = resourceSet.createResource(anotherFileURI);

a přidat objekt writer (spisovatele) tam, místo do prvního prostředku:

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

Tím by vznikly dva prostředky a každý z nich by obsahoval jeden objekt se vzájemným odkazem mezi dokumenty na druhý prostředek.

Všimněte si, že odkaz obsažení nutně znamená, že se obsažený objekt nachází ve stejném prostředku jako příslušný pořadač. Předpokládejme tedy, že jsme například vytvořili instanci Library (Knihovny) obsahující naši Book (Knihu) prostřednictvím odkazu obsažení books (knihy). To by automaticky odebralo Book (Knihu) z obsahu daného prostředku, který se v tomto smyslu také chová jako odkaz obsažení. Pokud bychom pak k prostředku přidali Library (Knihovnu), patřila by book (kniha) také implicitně do prostředku a její podrobnosti by v něm byly serializované.

Pokud chcete své objekty serializovat v jiném formátu než v XMI, je to možné. Budete potřebovat doplnit svůj vlastní kód pro serializaci a analýzu. Vytvořte si vlastní třídu prostředků (jako podtřídu třídy ResourceImpl), která implementuje serializační formát, kterému dáváte přednost, a potom ji buď lokálně zaregistrujte ve své množině prostředků, nebo v globálním registru továren, pokud chcete, aby byla s vaším modelem vždy používána.

Přizpůsobení objektů EMF

Když jsme se dříve dívali na metody set v generovaných třídách EMF, viděli jsme, že se upozornění posílají vždy, když se změní atribut nebo odkaz. Například metoda BookImpl.setPages() obsahovala následující řádky:

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

Každý EObject může udržovat seznam pozorovatelů (nazývaných také adaptéry), které budou upozorněny, kdykoli dojde ke změně stavu. Metoda struktury eNotify() prochází v iteracích tímto seznamem a předává pozorovatelům upozornění.

Pozorovatel může být přiřazen libovolnému objektu typu EObject (například book - knize) přidáním do seznamů adaptérů eAdapters následujícím způsobem:

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

Obvykle se ovšem adaptéry přidávají k objektům EObject pomocí továrny na adaptéry. Kromě své role pozorovatele jsou adaptéry často používány jako způsob, jak rozšířit chování objektu, ke kterému jsou připojeny. Klient obvykle připojí takové rozšířené chování tím, že požádá továrnu na adaptéry, aby adaptovala objekt s příponou požadovaného typu. Zpravidla to vypadá asi takto:

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

RequiredType obvykle představuje nějaké rozhraní podporované adaptérem. Tento argument může být například skutečná třída java.lang.Class pro rozhraní vybraného adaptéru. Vrácený adaptér pak může být přetypován dolů na požadované rozhraní asi takto:

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

Adaptéry se často používají tímto způsobem, aby rozšířily chování objektu bez vytváření podtříd.

Abychom ošetřili upozornění v adaptéru, potřebujeme potlačit metodu eNotifyChanged(), která je volaná na každý registrovaný adaptér metodou eNotify(). Typický adaptér implementuje v závislosti na typu upozornění metodu eNotifyChanged(), aby provedl určitou akci pro některá nebo všechna upozornění.

Někdy jsou adaptéry určeny k adaptování specifické třídy (například Book - Kniha). V takovém případě může metoda notifyChanged() vypadat asi takto:

  public void notifyChanged(Notification notification)
  {
    Book book = (Book)notification.getNotifier();
    switch (notification.getFeatureID(Book.class))
    {
      case LibraryPackage.BOOK__TITLE:
        // book title (název knihy) se změnil
        doSomething();
        break;
      caseLibraryPackage.BOOK__CATEGORY:
        // book category (kategorie knihy) se změnila
        ...
      case ...
    }
  }

Voláním notification.getFeatureID() je předán argument Book.class, aby se ošetřila možnost, že adaptovaný objekt není instancí třídy BookImpl, ale instancí nějaké podtřídy s vícenásobnou dědičností, kde Book (Kniha) není primárním (prvním) rozhraním. V takovém případě bude ID vlastnosti předaný v upozornění relativní číslo vzhledem k druhé třídě, a proto musí být upraven, než budeme moci přepnout pomocí konstant BOOK__. V situacích s jednoduchou dědičností je tento argument ignorován.

Další obvyklý typ adaptéru není vázaný na žádnou specifickou třídu, ale používá k provádění své funkce reflektivní API EMF. Místo volání metody getFeatureID() při upozornění může volat metodu getFeature(), která vrací skutečnou vlastnost Ecore (to jest objekt metamodelu, který tuto vlastnost představuje).

Použití reflektivního API

Každou generovanou třídou modelu může být také manipulováno pomocí reflektivního API definovaného v rozhraní EObject:

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

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

Pomocí reflektivního API můžeme nastavit název autora (author) následujícím způsobem:

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

nebo tento název získat takto:

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

Všimněte si, že vlastnost, k níž je přistupováno, se identifikuje metadaty získanými ze samostatné instance balíčku knihovny.

Použití reflektivního API je o něco méně efektivní než přímé volání generovaných metod getName() a setName()[19] , ale otevírá model pro zcela generický přístup. Reflektivní metody jsou používány například strukturou EMF.Edit pro implementaci úplné sady generických příkazů (například AddCommand, RemoveCommand, SetCommand), které lze použít s libovolným modelem. Podrobnosti viz Přehled struktury EMF.Edit.

Kromě metod eGet() a eSet() obsahuje reflektivní API dvě další spřízněné metody: eIsSet() a eUnset(). Metodu eIsSet() lze použít ke zjištění, zda je či není nějaký atribut nastaven[20] , metodu eUnset() lze použít ke zrušení jeho nastavení (nebo nastavení na původní hodnotu). Například generický serializátor XMI používá eIsSet(), aby určil, které atributy je třeba serializovat během operace ukládání prostředku.

Rozšířená témata

Příznaky pro řízení generování

Existuje několik příznaků, které je možné nastavit na vlastnost modelu, a tak řídit způsob generování kódu pro danou vlastnost. Zpravidla je výchozí nastavení těchto příznaků dostačující, takže je nemusíte měnit příliš často.

Datové typy

Jak již bylo zmíněno, všechny třídy definované v modelu (například Book - Kniha nebo Writer - Spisovatel) se implicitně odvozují ze základní třídy EMF EObject. Všechny třídy používané modelem však nutně nemusí být typu EObject. Předpokládejme například, že chceme do svého modelu přidat atribut typu java.util.Date. Než to budeme moci udělat, musíme definovat nějaký datový typ EMF DataType, který bude představovat tento externí typ. V UML používáme k tomuto účelu třídu se stereotypem datatype:

Definice datového typu

Jak je vidět, datový typ je pojmenovaný prvek modelu, který se je zástupcem nějaké třídy Java. Skutečná třída Java je poskytnuta jako atribut se stereotypem javaclass, jehož název je úplná třída, kterou představuje. Máme-li definovaný tento datový typ, můžeme teď deklarovat atributy typu java.util.Date následujícím způsobem:

Atribut s datovým typem Attribute jako typem atributu

Pokud regenerujeme, objeví se nyní v rozhraní Book (Kniha) atribut publicationDate:

  import java.util.Date;

  public interface Book extends EObject
  {
    ...

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

Jak vidíte, tento atribut typu Date (Datum) je ošetřen v podstatě stejně jako jakýkoli jiný. Vlastně všechny atributy včetně atributů typu Řetězec, interval a podobně mají jako svůj typ nějaký datový typ. Jediná věc, kterou se standardní typy Java odlišují, je, že jsou odpovídající typy předdefinované v modelu Ecore, takže je není třeba v každém modelu, který je používá, definovat znovu.

Definice datového typu má na generovaný model ještě jeden vliv. Protože datové typy představují určité libovolné třídy, generický serializátor a syntaktický analyzátor (například výchozí serializátor XMI) nemohou vědět, jak uložit stav atributu tohoto typu. Má zavolat toString()? To je rozumná výchozí alternativa, ale struktura EMF ji nechce vyžadovat, takže v tovární implementační třídě generuje pro každý datový typ definovaný v modelu dvě další metody:

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

Standardně tyto metody jednoduše vyvolají implementace supertříd, které zajistí rozumné, ale ne efektivní výchozí nastavení: convertToString() prostě volá toString() na instanceValue, avšak createFromString() se pokusí pomocí obrazu Java volat konstruktor typu String (Řetězec), nebo když se to nezdaří, statickou metodu valueOf(), existuje-li nějaká. Zpravidla byste měli tyto metody převzít (odstraněním značek @generated) a změnit na odpovídající vlastní implementace:

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

Model Ecore

Zde je uvedena kompletní hierarchie tříd modelu Ecore (stínované rámečky jsou abstraktní třídy):

Hierarchie tříd Ecore

Jak vidíte, zavádí malé množství tříd představujících artefakty EMF popsané v tomto dokumentu: třídy (a jejich atributy, odkazy a operace), datové typy, výčty, balíčky a továrny.

Implementace Ecore v EMF je sama o sobě generovaná pomocí generátoru EMF a jako taková má tutéž odlehčenou a efektivní implementaci, popsanou v předchozích oddílech tohoto dokumentu.



[1] Metamodel EMF je ve skutečnosti sám modelem EMF, jehož výchozí serializační formou je XMI.

[2] EMF v současnosti podporuje import z Rational Rose, ale architektura generátoru může bez problémů vyhovovat i jiným modelovacím nástrojům.

[3] EMF používá podmnožinu jednoduchých způsobů pojmenování přístupových mechanismů vlastností komponenty JavaBean.

[4] Existuje několik voleb, specifikovatelných uživatelem, které lze použít ke změně generovaných vzorů. Některé z nich popíšeme později (viz Příznaky pro řízení generování, dále v tomto dokumentu).

[5] Odkazy obsažení, které popíšeme později, (viz Odkazy obsažení), nemohou zahrnovat dokumenty. Také existuje příznak, který mohou uživatelé nastavit v metadatech odkazu, aby označili, že není třeba volat rozpoznání, protože daný odkaz nebude nikdy použit ve scénáři vzájemných odkazů mezi dokumenty (viz Příznaky pro řízení generování). V těchto případech generovaná metoda get vrací jednoduše ukazatel.

[6] Aplikace, které musí pracovat s nefunkčními odkazy a ošetřit je, by měly pro objekt vrácený metodou get volat eIsProxy(), aby zjistily, zda je vůbec rozpoznaný (například book.getAuthor().eIsProxy()).

[7] To evidentně neumožňuje použití více autorů, ale model je o to jednodušší.

[8] Důvodem, proč se vůbec obtěžujeme provést delegování na metodu basicSet(), je, že ji vyžadují také metody eInverseAdd() a eInverseRemove(), na které se podíváme o něco později.

[9] Všechny konkrétní implementace EList jsou ve skutečnosti jednoduché podtřídy jedné vysoce funkční a efektivní základní implementační třídy EcoreEList.

[10] V metodě eInverseAdd() se namísto jednoduchého přepnutí na dodané id vlastnosti nejprve volá eDerivedStructuralFeatureID(featureID, baseClass). U jednoduchých modelů s jednoduchou dědičností má tato metoda výchozí implementaci, která ignoruje druhý argument a vrací předaný featureID. U modelů, které využívají vícenásobnou dědičnost, může mít eDerivedStructuralFeatureID() generované potlačení, které upraví ID vlastnosti týkající se přidané třídy (tedy baseClass) na ID vlastnosti týkající se konkrétní odvozené třídy příslušné instance.

[11] EObjectImpl má také proměnnou instance eContainerFeatureID typu interval (int), určenou ke sledování toho, který odkaz je právě používán pro eContainer.

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

[13] Aby byl dodržen řádný styl programování v jazyce Java, jsou názvy statických konstant převedeny na velká písmena, pokud literálové názvy modelovaných výčtů velká písmena již neměly.

[14]  Váš program sice nemusí nutně používat rozhraní Factory nebo Package, EMF však podporuje klienty v používání továrny k vytváření instancí generováním chráněných konstruktorů pro třídy modelu, čímž brání jednoduše zavolat metodu new při vytváření vašich instancí. Pokud to však opravdu chcete, můžete v generovaných třídách manuálně změnit přístup na public (veřejný). Pokud se později rozhodnete třídy regenerovat, nebudou vaše předvolby přepsány.

[15] První základní třída v modelu Ecore je ve skutečnosti používána jako základní implementační třída. V diagramu UML je potřebný stereotyp <<extend>> pro indikaci, že má být Book (Kniha) první ve znázornění Ecore.

[16] Pokud předem víte, že budete chtít zajistit svou vlastní implementaci nějaké vlastnosti, je lepší modelovat příslušný atribut jako volatile (nestálý), což generátoru říká, že má vygenerovat jen kostru těla metody, a od vás se pak očekává, že ji implementujete.

[17] EMF obsahuje generický mechanizmus pro anotaci objektů metamodelu s doplňujícími informacemi. Tento mechanizmus lze použít také pro připojení uživatelské dokumentace k prvkům modelu, a je-li model vytvořen ze schématu XML Schema, spoléhá EMF, že zachytí podrobnosti serializace, které nelze vyjádřit přímo pomocí Ecore.

[18] Výše uvedený kód selže, pokud bude spuštěn samostatně (tj. přímo vyvolán v JVM s požadovanými soubory JAR EMF na cestě ke třídě), protože neinicializuje registr továrny na prostředky pomocí výchozí továrny na prostředky. Následující řádek by měl být vložen okamžitě po vytvoření množiny prostředků:

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

Potom bude prostředek používat k serializaci implementaci prostředku s výchozím XMI. Stejná registrace se automaticky provede v globálním registru továren na prostředky, pokud je EMF spuštěno z Eclipse.

[19] Implementace reflektivních metod jsou generované také pro každou třídu modelu. Provedou přepnutí na typ vlastnosti a jednoduše zavolají příslušné generované metody typu typesafe.

[20] Informace o tom, co tvoří nastavený atribut, najdete v oddíle Příznaky pro řízení generování pod tématem příznaku Unsettable.

[21] Než deklarujete, že vlastnost nerozpoznává zástupce, vše pečlivě promyslete. Pouhá skutečnost, že daný odkaz nepotřebujete použít v situaci se vzájemnými odkazy mezi dokumenty, ještě neznamená, že to nebude potřebovat někdo jiný, kdo chce váš model používat. Deklarovat, že vlastnost nerozpoznává zástupce, je podobné jako deklarovat třídu Java jako konečnou (final).