Az EMF.Edit keretrendszer áttekintése

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

A dokumentum megértéséhez szükséges az alapvető EMF (Eclipse Modeling Framework) fogalmak ismerete. További információk Az Eclipse Modeling Framework (EMF) áttekintése című dokumentumban találhatók.

Bevezetés

Ha egy EMF-alapú modellből az EMF-generátor segítségével előzőleg már előállította a kódot, és most szeretné kiegészíteni a modellt és a kódot felhasználói felületekkel, akkor az EMF.Edit segítségével megkönnyítheti ezt a munkát.

Az EMF.Edit egy olyan Eclipse keretrendszer, amely tartalmaz EMF-szerkesztők felépítésére alkalmazható általános, újra felhasználható osztályokat. Az EMF.Edit az alábbi szolgáltatásokat nyújtja:

Ebben a dokumentumban áttekintjük az EMF.Edit keretrendszer és a generátor eszköz alapvető fogalmait. Ennél alaposabb leírást tartalmaz a keretrendszer osztályainak dokumentációja, melyek részletesen taglalják azok viselkedését és képességeit.

EMF-objektumok megtekintése JFace megjelenítőkben

Mi a tartalomszolgáltató?

Az Eclipse felhasználói felület keretrendszere (JFace) újra felhasználható megjelenítő osztályok készletét tartalmazza (például TreeViewer, TableViewer), melyek strukturált modellek megtekintésére használhatók. A JFace megjelenítők tetszőleges típusú objektumot képesek kezelni (vagyis bármilyen java.lang.Object alosztályt), azaz nem követelik meg, hogy a megtekintendő objektumok valamilyen adott protokollnak feleljenek meg (vagyis azt, hogy hogyan valósítsanak meg egy bizonyos felületet). Ez azért lehetséges, mert a megjelenítők a modell objektumainak közvetlen navigálása helyett egy tartalomszolgáltató elnevezésű illesztőobjektumon keresztül férnek hozzá azokhoz.

Minden egyes megjelenítő egy adott szolgáltatófelületet megvalósító tartalomszolgáltatót alkalmaz. A TreeViewer például az alábbi felületet megvalósító tartalomszolgáltatót használja:

  public interface ITreeContentProvider ...
  {
    public Object[] getChildren(Object object);
    public Object getParent(Object object);
    ...
  }

Az alábbi ábra mutatja az alapvető szerkezetet:

JFace tartalomszolgáltató

A TreeViewer objektumok (saját nyelvén: elemek) struktúráját jeleníti meg a képernyőn, melyeket - a bemeneti (fő) objektumot kivéve - a getChildren() metódusnak saját tartalomszolgáltatóján történő meghívása révén szerez.

A rendszer a többi megjelenítőt is hasonlóképpen kezeli, de mindegyik megjelenítőnek szüksége van a saját felületét megvalósító tartalomszolgáltatóra. Annak ellenére, hogy a megjelenítők felületei különböznek egymástól, a tartalomszolgáltató gyakran több felületet is meg tud egyszerre valósítani, mely lehetővé teszi egy tartalomszolgáltató többféle megjelenítő általi használatát.

Az EMF-modell tartalmának megadása

Az EMF.Edit keretrendszer olyan általános tartalomszolgáltató megvalósítási osztályt biztosít, melyet az EMF-modellek tartalmának megadásra lehet használni. Az AdapterFactoryContentProvider osztály úgy valósítja meg a tartalomszolgáltató felületeket, hogy megbízza azokat az EMF-illesztőket, amelyek képesek a modellobjektumoknak (elemeknek) a megjelenítők számára történő navigálására. A famegjelenítőt támogató EMF-illesztő osztály például a következő EMF.Edit felületet valósítaná meg:

  public interface ITreeItemProvider
  {
    public Collection getChildren(Object object);
    public Object getParent(Object object);
    ...
  }

Vegyük észre, hogy ez a felület mennyire hasonlít a fentebb leírt ITreeContentProvider tartalomszolgáltató felülethez. Az AdapterFactoryContentProvider úgy valósítja meg a tartalomszolgáltató felületet, hogy először megkeresi, majd megbízza az adott elem illesztőjét (mely megvalósítja az elemszolgáltató felületet). A terminológia objektumról elemre módosítása szándékos: a megjelenítő szempontjából elemekről és nem objektumokról van szó.

Az EMF keretrendszerben mindez így fest:

Az AdapterFactoryContentProvider megbízza az elemszolgáltatókat

Megjegyzés: adott EMF-modell esetén az ItemProviderAdapterFactory és ItemProvider osztályokat automatikusan elő lehet állítani az EMF.Edit keretrendszerben biztosított generátor segítségével. Később ezt bővebben tárgyaljuk.

Az AdapterFactoryContentProvider tartalomszolgáltatót egy olyan illesztőgyár állítja össze, amely - más EMF illesztőgyárhoz hasonlóan - bizonyos (ebben az esetben ItemProviders) típusú illesztők készítésére és megkeresésére szolgál. A tartalomszolgáltató a getChildren() metódustól érkező kérést az adapt(item) metódusnak az ItemProviderAdapterFactory szolgáltatón történő meghívásával teljesíti, mely létrehozza, illetve visszaadja az ItemProvider (adapter) metódust a megadott elemre. Ezek után egyszerűen megbízza a kívánt felület (ebben az esteben az ITreeItemProvider) getChildren() metódusát.

Az AdapterFactoryContentProvider getChildren() metódusa valahogy így néz ki:

  public Object[] getChildren(Object object)
  {
    ITreeItemContentProvider adapter =
      (ITreeItemContentProvider)
        adapterFactory.adapt(object, ITreeItemContentProvider.class);
    return adapter.getChildren(object).toArray();
  }

Az összes tartalomszolgáltató metódus ugyanezt a mintát használja. Mint azt korábban már említettük, az AdapterFactoryContentProvider egyszerűen delegálja a tartalomszolgáltató metódusokat annak az elemszolgáltatónak (illesztőnek), amelyik ki tudja szolgálni a kérést.

A fenti getChildren() metódus esetében az adapterFactory.adapt() metódusnak átadott objektum egy egyszerű java.lang.Object (de nem org.eclipse.emf.ecore.EObject). Ez az EMF.Edit keretrendszer egy fontos jellemzője. A keretrendszer - körültekintő tervezése és felépítése folytán - az EMF-modellek olyan megjelenítését is ellátja, mely esetleg különbözik magától a modelltől (vagyis olyan megjelenítéseket, melyek elnyomnak objektumokat vagy tartalmaznak további - képzeletbeli - objektumokat). Az EMF- és nem EMF-objektumok ilyen keverékét az teszi lehetővé, hogy a keretrendszer illesztőgyárainak alaposztálya az adapt() metódust alábbiak szerint valósítja meg:

  public Object adapt(Object object, Object type)
  {
    if (object instanceof Notifier)
      return this.adapt((Notifier)object, type);
    else
      return object;
  }

Ha a megadott objektum nem EMF Értesítő[1] , akkor a metódus magát az objektumot adja vissza. Ez a konstrukció lehetővé teszi, hogy az elemszolgáltatók a megjelenítéshez nem EMF-objektumokat is felvehessenek az által, hogy nem EMF-objektumot ad vissza (például saját getChildren() metódusa segítségével). Amíg a visszadott objektum megvalósítja a megjelenítő kívánt elemszolgáltató felületét (például az ITreeItemProvider felületet), a rendszer ugyanúgy fogja kezelni, mint bármilyen más EMF-elemet.

A konstrukció ezen aspektusa azt is kiemeli, hogy miért szeretjük a szolgáltató-/illesztőosztályokat elemszolgáltatóknak és nem illesztőknek nevezni. Nem triviális alkalmazás esetén a megjelenítő modell (vagyis a megjelenítő tartalomszolgáltatója által biztosított modell) gyakran kétféle objektum keveréke: egyrészt olyan "valós" (EMF) modellobjektumokból áll, melyek elemszolgáltatói ugyancsak (EMF) illesztők, másrészt "képzeletbeli " objektumokból, melyek elemszolgáltatója maga az objektum. Tehát minden illesztő egyben elemszolgáltató is, de ennek fordítottja nem feltétlenül igaz.

JFace címkeszolgáltatók

Az előző szakaszban leírtuk, hogy a JFace megjelenítők hogyan használják fel a tartalomszolgáltatókat a tartalmi elemek megszerzésére. A megjelenítő által kijelzett elemek címkeképének és -szövegének lekérésére is hasonló megközelítést használ a rendszer. A megjelenítő egy másik objektumot, a tartalomszolgáltatóhoz hasonló címkeszolgáltatót használja az elemek címkéinek lekérésére a helyett, hogy azokat maguktól az elemektől szerezné be. A TreeViewer például a fa elemeinek címkéit úgy szerzi be, hogy ezzel megbízza az ILabelProvider felületet megvalósító objektumot.

EMF-objektumok felcímkézése

Az EMF.Edit keretrendszer ugyanazt a mechanizmust használja az EMF-modellek címkeszolgáltatóinak megvalósításához, mint amit a tartalom biztosításához. Egy általános címkeszolgáltató megvalósítási osztály, az AdapterFactoryLabelProvider (mely pontosan ugyanúgy működik, mint az AdapterFactoryContentProvider) megbízza a modell elemszolgáltatóinak ILabelProvider felületét (ugyanazokat az elemszolgáltatókat, amelyek a tartalmat is szállítják). A kibővített ábra így fest:

Az AdapterFactoryLabelProvider megbízza az elemszolgáltatókat

A tartalom- és címkeszolgáltató megbízhatja (és ezt általában meg is teszi) ugyanazt az illesztőgyárat és - következésképpen - ugyanazokat az elemszolgáltatókat. Az elemszolgáltatók - a tartalomszolgáltatókhoz hasonlóan - ott találhatók, ahol a munkát a rendszer valójában elvégzi.

Az EMF.Edit szolgáltatóosztályok segítségével a felhasználó az alábbiak szerint állíthatja össze egy EMF-modell TreeViewer megjelenítőjét:

  myAdapterFactory = ...

  treeViewer = new TreeViewer();

  treeViewer.setContentProvider(new AdapterFactoryContentProvider(myAdapterFactory));
  treeViewer.setLabelProvider(new AdapterFactoryLabelProvider(myAdapterFactory));

Ezt a TreeViewer megjelenítőt aztán meg lehet tekinteni például egy szerkesztői ablakban a szokásos (a JFace által előírt) módon.

Mindez első látszatra nagyon triviálisnak tűnhet. Ez azért van, mert eddig csak arról volt szó, hogy hogyan bízzunk meg valaki mást (vagyis az illesztőgyárról). Még nem tárgyaltuk a metódusok megvalósítását, eddig csak delegáltuk azokat. A metódus megvalósítását azonban az EMF.Edit támogatja, beleértve a kódgenerátort, mely az elemszolgáltató és gyári kód nagy részét előállítja. Mielőtt ennek taglalásába belefognánk, nézzük meg, hogy az elemszolgáltatók hogyan végzik munkájukat.

Elemszolgáltatók megvalósítási osztályai

Mint azt az előző szakaszban láthattuk, az EMF-modellek számára nyújtott valódi,tartalomszolgáltatási tevékenységet a modellhez csatlakoztatott elemszolgáltató illesztők végzik. Az ItemProvider illesztőinek számát és típusát a fenti diagramban szándékosan nem határoztuk meg pontosan. Ezt azért tettük, mert az EMF.Edit keretrendszer az elemszolgáltató illesztők két különböző mintáját támogatja:

  1. Állapotteljes illesztők - egy-egy illesztő a modell minden egyes objektumára
  2. Egyke illesztők - egy-egy illesztőobjektum a modell minden egyes objektumtípusára (lehetséges esetekben ajánlott)

Az adott modell elemszolgáltatóit a két minta bármelyikével, vagy a kettő keverékével is meg lehet valósítani.

Állapotteljes elemszolgáltató illesztők

Az első mintánál minden egyes objektum egy az egyben megfelel saját illesztőjének. Mindegyik illesztő rendelkezik egy mutatóval (úgynevezett céllal), mely az általa illesztett egyetlen objektumra mutat.

Az ábra így fest:

Állapotteljes elemszolgáltató illesztők

Mint látható, a minta megduplázza az alkalmazás objektumait, és ezáltal kizárólag olyan alkalmazásoknál ésszerű, melyben az egyedeknek további állapotokat kell viselniük. Ez az oka annak, hogy a minta elnevezése Állapotteljes.

Egyke elemszolgáltató illesztők

Az Egyke minta a jobb megközelítés, mivel ez elkerüli a többletobjektumok nagy részét. Ennél a mintánál egyetlen elemszolgáltató illesztőt használunk fel az összes egyező típusú elemre. Ez így néz ki:

Egyke elemszolgáltató illesztők

A fenti ábrán az objektumok szokás szerint az illesztőkre mutatnak, de az elemszolgáltatók (melyek megosztottak) nem mutatnak vissza az objektumokra. Emlékezzünk vissza a tartalomszolgáltató szakaszban korábban megvizsgált fa elemszolgáltató felületre, melynél minden metódus egy extra paraméterrel (objektummal) rendelkezett:

  public interface ITreeItemProvider
  {
    public Collection getChildren(Object object);
    public Object getParent(Object object);
    ...
  }

Az "object" (objektum) argumentum felvétele minden egyes elemszolgáltatóhoz kifejezetten ennek a mintának a támogatása céljából történt. Az Állapotteljes esetben ez objektum mindig meg fog egyezni az illesztő céljával.

Felmerülhet a kérdés, hogy a rendszer miért nem támogatja a "valódi" Egyke illesztőmintát - vagyis, hogy pontosan egy illesztő jusson az összes objektumra. A válasz egyszerűen az, hogy - bár ez egy lehetséges minta (mely kompatibilis az EMF.Edit keretrendszerrel)[2]  - nem javasoljuk ezt a megoldást, mivel az ilyen teljesen dinamikus megvalósítást egyszerűsége miatt rendkívül nehéz személyre szabni (csúnya instanceof() ellenőrzések garmadája nélkül). A modell öröklési hierarchiáját tükröző típusos elemszolgáltató osztályok alternatívája kényelmes kiindulási pontot biztosít a modell objektumorientált megjelenítési kódjának tetszetős, tiszta megvalósításához.

Az EMF-modell módosítása parancsok segítségével

Eddig még csak azt tárgyaltuk, hogy az EMF-modellt hogyan lehet megjeleníteni tartalom- és címkeszolgáltatók használatával. Az EMF.Edit keretrendszer egy másik jellemzője a modell parancsalapú szerkesztésének támogatása. A "szerkesztés" kifejezés alatt visszavonható módosítást értünk, szemben a modell egyszerű "megírásával".

Szerkesztési tartományok

Az EMF.Edit EditingDomain (SzerkesztésiTartomány) felületét EMF-modellek szerkesztés céljából történő hozzáférésére lehet használni. Az EMF.Edit AdapterFactoryEditingDomain megvalósítási osztálya - hasonlóan a tartalom- és címkeszolgáltatókhoz - a megvalósítással megbízza az elemszolgáltatókat (az ItemProviderAdapterFactory illesztőgyáron keresztül):

Tartomány szerkesztése

Mint látható, a felület egy parancsveremhez is biztosít hozzáférést, melyen keresztül a modell módosításai elvégezhetők. A szerkesztési tartomány két alapvető szolgáltatást nyújt:

A szerkesztési tartományt úgy lehet a legjobban elképzelni, mint a modell módosító vagy írási szolgáltatóját, míg a tartalom- és címkeszolgáltatók a megjelenítő vagy olvasási szolgáltatók. Ennek felsőszintű áttekintése az alábbiak szerint alakul:

Írási és olvasási szolgáltatók

A modell módosítása

Vegyünk a modell módosítására egy egyszerű példát.

Tételezzük fel, hogy a "Company" (Társaság) osztály rendelkezik egy egy-a-sokhoz hozzárendelési típusú hivatkozással (melynek elnevezése "departments" - részlegek) a "Department" (Részleg) osztályhoz. Egy részlegnek a társaságból történő eltávolítását (például a szerkesztő törlési műveletét kivitelezendő) az alábbiak szerint lehet kódolni:

  Department d = ...
  Company c = ...
  c.getDepartments().remove(d);

Egyszerűségénél fogva a fenti kód pusztán a módosítást végzi el.

Ha e helyett az EMF.Edit eltávolítási parancsát használjuk (org.eclipse.emf.edit.command.RemoveCommand) a részleg eltávolításához, az így alakul:

  Department d = ...
  Company c = ...
  EditingDomain ed = ...
  RemoveCommand cmd = 
    new RemoveCommand(ed, c, CompanyPackage.eINSTANCE.getCompany_Departments(), d);
  ed.getCommandStack().execute(cmd);

A részleg ilyen módon történő eltávolítása számos előnnyel jár:

  1. A törlés visszavonható az ed.getCommandStack().undo() metódus meghívásának segítségével.
  2. Ki lehet deríteni, hogy a modell rendezetlen sorsú-e (például a mentés menü engedélyezésének céljából) az által, hogy ellenőrizzük, van-e parancs a parancsveremben - feltéve, hogy az összes módosítás parancsok használatával történt (melyet az EMF.Edit keretrendszer pártol).
  3. Egy parancs végrehajtása előtt lehetőség van ellenőrizni annak érvényességét (például a törlés menü engedélyezése céljából) a cmd.canExecute() metódus hívásával.

Parancsok ilyen átható módon történő használata révén lehet az EMF.Edit keretrendszer által nyújtott funkciók teljes palettáját kihasználni.

Parancsok készítése szerkesztési tartományok segítségével

Az előző példában létrehoztuk a RemoveCommand (EltávolításParancs) parancsot a "new" (új) hívás egyszerű használata segítségével. Ez jól működik, de a funkció nem kifejezetten használható fel újra; a kódtöredék egy nagyon specifikus dolgot végez el, nevezetesen egy részleg eltávolítását a társaságból. Ha e helyett mondjuk egy olyan újra felhasználható törlési műveletet szeretnék megírni, amely képes lenne bármilyen objektum törlésére, akkor ezt az EditingDomain segítségével tehetjük meg.

Az EditingDomain felület többek között tartalmaz egy createCommand() nevű parancsgyár metódust, melyet parancsok készítésére lehet használni:

  public interface EditingDomain
  {
    ...
    Command createCommand(Class commandClass, CommandParameter commandParameter);
    ...
  }

Ha ennek a metódusnak a segítségével szeretne készíteni egy parancsot, akkor először egy CommandParameter (ParancsParaméter) objektumot kellene létrehoznia, ebben beállítania a parancs paramétereit, majd meghívnia a "create" (létrehozás) metódust a kívánt parancsosztály (például a RemoveCommand.class) és a paraméterek átadása mellett.

Ügyfeleinknek azonban nem kell mindezen keresztül menniük, mert az összes parancsosztályban rendelkezésre állnak a statikus kényelmi create() metódusok. A statikus create() metódus használata segítségével a RemoveCommand létrehozása és végrehajtása így fest:

  Department d = ...
  EditingDomain ed = ...
  Command cmd = RemoveCommand.create(ed, d);
  ed.getCommandStack().execute(cmd);

Látszólag ez csak egy kis szintaktikai módosítással jár ("RemoveCommand.create()" a "new RemoveCommand helyett"), azonban itt alapvető különbségekről van szó. A szerkesztési tartományon kívül csak egy argumentumot adtunk át (nevezetesen az eltávolítandó objektumot) a korábbi három helyett. Figyeljük meg, hogyan használható ez a kóddarab bármilyen objektum eltávolítására. A parancs létrehozásának a szerkesztési tartományra történő delegálása lehetővé teszi, hogy a hiányzó paramétereket az utóbbi töltse ki.

Hogyan kezeli a Szerkesztési Tartomány a createCommand() kérést?

Az egész folyamat megértése céljából kövessük nyomon a RemoveCommand.create() hívást. Ahogy arra előzőleg is felhívtuk a figyelmet, a statikus create() metódus mindössze egy olyan kényelmi metódus, amely az alábbiakat delegálja a szerkesztési tartománynak:

  public static Command create(EditingDomain domain, Object value) 
  {
    return domain.createCommand(
      RemoveCommand.class,
      new CommandParameter(null, null, Collections.singleton(value)));
  }

Az AdapterFactoryEditingDomain ezt a kérést elfogadja, és - a szabványos megbízási mintát használva - továbbadja azt egy elemszolgáltatónak (mint ahogy az AdapterFactorContentProvider delegálta a getChildren() metódust korábban):

  public Command createCommand(Class commandClass, CommandParameter commandParameter)
  {
    Object owner = ...  // a parancs tulajdonos objektumának lekérése
    IEditingDomainItemProvider adapter =
      (IEditingDomainItemProvider)
        adapterFactory.adapt(owner, IEditingDomainItemProvider.class);
    return adapter.createCommand(owner, this, commandClass, commandParameter);
  }

Megjegyzés: Ha megvizsgálja a tényleges createCommand() metódust, akkor észre fogja venni, hogy az ennél jóval összetettebb. Ez azért van, mert a metódus többek között objektumkollekciók egy lépésben történő törlését is kezelni tudja. Mindamellett ez fogalmilag lefedi a funkcióját.

A createCommand() metódus tulajdonosi objektumot használ a megbízandó elemszolgáltató elérésére (vagyis a tulajdonost használja az adapterFactory.adapt() hívásban). Példánkban a tulajdonos lesz a társaság objektuma (vagyis az eltávolítandó részleg szülője). A szerkesztési tartomány a tulajdonost úgy határozza meg, hogy meghívja a getParent() metódust a törlendő objektum elemszolgáltatóján.

Ez azt fogja eredményezni, hogy végül is a createCommand() metódust hívja meg a rendszer az eltávolítandó objektum szülőjének elemszolgáltatóján (az eredeti kódtöredékben a "c" társaság CompanyItemProvider szolgáltatója). Vagyis a CompanyItemProvider a következőképpen valósíthatná meg a createCommand() metódust:

  public class CompanyItemProvider ...
  {
    ...

    public Command createCommand(final Object object, ..., Class commandClass, ...)
    {
      if (commandClass == RemoveCommand.class)
      {
        return new RemoveCommand(object,
                                 CompanyPackage.eINSTANCE.getCompany_Departments(),
                                 commandParameter.getCollection()); 
      }
    ...
   }
  }

Ez ebben a formában is megfelelne, de ennél van egy jobb megoldás.

Minden elemszolgáltató osztály (mely egyben EMF-illesztő is) annak az ItemProviderAdapter EMF.Edit kényelmi alaposztályának a kiterjesztése, mely többek kötött tartalmazza a createCommand() alapértelmezett megvalósítását. Ez az alaposztály úgy valósítja meg a createCommand() metódust az EMF.Edit keretrendszer összes szabványos parancsára, hogy meghív néhány olyan egyszerű (de más célt is szolgáló) metódust, melyek az elemszolgáltató alosztályokban rendelkezésre állnak. Ez a Template Method (Sablonminta) tervezési minta egy példája.

A RemoveCommand példa működőképessé tételéhez a CompanyItemProvider szolgáltatásnak mindössze az alábbi metódust kell megvalósítania:

  public Collection getChildrenFeatures(Object object)
  {
    return Collections.singleton(CompanyPackage.eINSTANCE.getCompany_Departments());
  }

Mint látható, a metódus legalább egy olyan jellemzőt ad vissza (ebben az esteben csak a "departments" hivatkozást), melyet a rendszer az objektum leszármazottaira való utalásra használ. A metódus hívását követően a createCommand() alapértelmezett megvalósítása eldönti, hogy melyik jellemzőt kell használni (ha abból több érkezik), majd a megfelelővel létrehozza a RemoveCommand szolgáltatást.

Parancsok újradefiniálása

A parancsok szerkesztési tartományon keresztüli létrehozásának másik előnye az, hogy különféle alosztályokat és szabvány parancsok teljesen eltérő megvalósításait lehet létrehozni és csatlakoztatni, melyeket az általános szerkesztők egyből hasznosíthatnak. . Tegyük fel például, hogy amikor eltávolítunk egy társaság részlegét, akkor egy kis extra takarítást is szeretnénk végezni. Ennek legegyszerűbb módja az lehet, ha létrehozzuk a RemoveCommand osztály RemoveDepartmentCommand (RészlegEltávolításaParancs) alosztályát az alábbiak szerint:

  public class RemoveDepartmentCommand extends RemoveCommand
  {
    public void execute()
    {
      super.execute();
      // az extra dolog elvégzése...
    }
  }

Ez a része elég könnyű.

Ha a szerkesztő a statikus RemoveCommand.create() metódust használja (mely az editingDomain.createCommand() metódust hívja) a "new RemoveCommand()" hívás helyett, akkor a szabványos RemoveCommand parancsot könnyedén helyettesíthetjük saját RemoveDepartmentCommand parancsunkkal a createCommand() elemszolgáltatóbani újradefiniálása segítségével az alábbiak szerint:

  public class CompanyItemProvider ...
  {
    ...

    public Command createCommand(final Object object, ...)
    {
      if (commandClass == RemoveCommand.class)
      {
        return new RemoveDepartmentCommand(...); 
      }
      return super.createCommand(...);
    }
  }

Ha az előre meghatározott parancsok egyikét (mint például a RemoveCommand parancsot) szeretnénk személyre szabni, akkor a helyettesítés még könnyebb, mivel a createCommand() alapértelmezett megvalósítása elindítja az összes parancs parancsspecifikus kényelmi metódusának létrehozását a következőképpen:

  public Command createCommand(final Object object, ...
  {
    ...
    if (commandClass == RemoveCommand.class)
      return createRemoveCommand(...); 
    else if (commandClass == AddCommand.class)
      return createAddCommand(...);
    else ...
  }

Tehát a RemoveDepartmentCommand parancsot is egyszerűbben létre tudtuk volna hozni úgy, hogy ha a createRemoveCommand() metódust definiáljuk újra a createCommand() helyett:

  protected Command createRemoveCommand(...) 
  {
    return new RemoveDepartmentCommand(...);
  }

Összegezve: a lényeg az, hogy a parancsok paramétereinek igazításához (beleértve magát a parancsosztályt is) célszerű a szerkesztési tartományt csatlakozópontként felhasználni, melylehetővé teszi azt, hogy a modell szerkesztési parancsainak viselkedését kényelmesen vezéreljük.

Értesítés a modell módosításáról

Nem beszéltünk még a módosítási értesítésekről. Hogyan lehet a megjelenítőket frissítésre késztetni, amikor egy parancs módosít valamit a modellben? A válasz az, hogy ez a szabvány EMF- illesztő értesítés és az EMF.Edit által nyújtott, a megjelenítők frissítésére szolgáló mechanizmus kombinációja segítségével működik.

Egy AdapterFactoryContentProvider tartalomszolgáltató összeállításakor a szolgáltató az org.eclipse.emf.edit.provider.IChangeNotifier felületet megvalósító illesztőgyár figyelőjeként (org.eclipse.emf.edit.provider.INotifyChangedListener) jegyzi be magát. Az illesztőgyár az összes általa létrehozott elemszolgáltatónak átadja magát, mely által a modell központi módosítási értesítőjévé válhat. Az AdapterFactoryContentProvider tartalomszolgáltató (az inputChanged() metódusban) ugyancsak rögzíti azt a megjelenítőt, amely számára tartalmat biztosít, hogy módosítási értesítés kézhezvételekor frissíthesse megjelenítőjét.

Az alábbi ábra mutatja, hogy az EMF-modell egyik objektumában történő változás (például cégnév módosítása) hogyan halad az illesztőgyáron keresztül a modell megjelenítői felé.

Értesítés a modell módosításáról

Amikor az egyik EMF-objektum állapotot vált, a rendszer a notifyChanged() metódust hívja meg az objektum összes illesztőjén, beleértve az elemszolgáltatókat is (ebben az esetben a CompanyItemProvider szolgáltatót). Az elemszolgáltató notifyChanged() metódusa felelős annak eldöntéséért, hogy minden egyes esemény értesítését át kell-e adni a megjelenítőnek, és ha igen, akkor az milyen típusú frissítést kell, hogy eredményezzen.

Ebből a célból érdekes értesítéseket csomagol a ViewerNotification értesítőbe, mely az IViewerNotification egyszerű megvalósítása. Ez a felület a következőképpen terjeszti ki az alapszintű "Notification" (Értesítés) felületet:

  public interface IViewerNotification extends Notification
  {
    Object getElement();
    boolean isContentRefresh();
    boolean isLabelUpdate();
  }

Ezek a metódusok határozzák meg, hogy a megjelenítő melyik elemét kell frissíteni, frissíteni kell-e az elem alatti tartalmat, valamint hogy az elem címkéjét kell-e frissíteni. Mivel az elemszolgáltató határozza meg egy objektum leszármazottjait és annak címkéjét, ugyancsak el kell döntenie, hogy hogyan lehet a megjelenítőt hatékonyan frissíteni.

A CompanyItemProvider szolgáltató notifyChanged() metódusa így néz ki:

  public void notifyChanged(Notification notification)
  {
    ...
    switch (notification.getFeatureID(Company.class))
    {
      case CompanyPackage.COMPANY__NAME:
        fireNotifyChanged(new ViewerNotification(notification, ..., false, true));
        return;
      case CompanyPackage.COMPANY__DEPARTMENT:
        fireNotifyChanged(new ViewerNotification(notification, ..., true, false));
        return;
    }
    super.notifyChanged(notification);
  }

Ebben a megvalósításban a "name" attribútum változása címkefrissítést eredményez, a "department" hivatkozás módosítása pedig tartalomfrissítéssel jár. A többi módosítási értesítés nincs hatással a megjelenítőre.

A fireNotifyChanged() metódus az ItemProviderAdapter osztály (az összes elemszolgáltató illesztő alaposztálya) egy olyan kényelmi metódusa, amely egyszerűen továbbítja az értesítést az illesztőgyárnak.[3] . Az illesztőgyár (módosítási értesítő) hozzálát az értesítéseknek az összes figyelője felé történő szétküldéséhez (ebben a példában csak a famegjelenítő tartalomszolgáltatójának). Végül a tartalomszolgáltató az értesítés által jelzett módon frissíti a megjelenítőt.

Képzett illesztőgyárak

Az EMF-modelleket gyakran modelleket átívelő hivatkozások kötik össze. Amikor olyan alkalmazást szeretne felépíteni, amely több EMF-modellt átívelő objektumok szerkesztésére vagy megjelenítésére szolgál, akkor olyan illesztőgyárra van szüksége, amely képes a két (vagy több) modellből származó objektum uniójának illesztésére.

Gyakran az egyes modellek már rendelkeznek illesztőgyárral, és csak össze kellene őket ragasztani. Erre a célra egy másik EMF.Edit kényelmi osztályt, a ComposedAdapterFactory (KépzettIllesztőGyár) gyárat lehet használni:

Illesztőgyár képzése

A ComposedAdapterFactory gyár közös felületet biztosít a többi illesztőgyár felé, melyeket saját megvalósításával egyszerűen megbízza.

Képzett illesztőgyár létrehozását például ilyen kóddal lehet elérni:

  model1AdapterFactory = ...
  model2AdapterFactory = ...

  ComposedAdapterFactory myAdapterFactory = new ComposedAdapterFactory();
  myAdapterFactory.addAdapterFactory(model1AdapterFactory);
  myAdapterFActory.addAdapterFActory(model2AdapterFactory);

  myContentProvider = new AdapterFactoryContentProvider(myAdapterFactory);
  ...

Az EMF.Edit kódgenerátor használata

Megjegyzés: EMF-modell, valamint EMF.Edit szerkesztő készítésének részletes ismertetője az Ismertető: EMF-modell készítése című dokumentumban található.

Egy adott EMF-modell definícióra az EMF.Edit kódgenerátor képes előállítani egy teljesen funkcionális szerkesztőeszközt, amely lehetővé teszi a modell egyedeinek általános megjelenítők általi megtekintését, modellobjektumok felvételét, eltávolítását, kivágását, másolását és beillesztését, valamint objektumok szabvány tulajdonságlapon történő módosítását - mindezt a műveletek visszavonásának és ismétlésének teljes támogatása mellett.

Az EMF.Edit generátor teljesen működőképes bedolgozókat állít elő, melyek az alábbiakat tartalmazzák:

  1. ItemProviderAdapterFactory (ElemSzolgáltatóIllesztőGyár)
  2. ItemProviders (ElemSzolgáltatók - modellosztályonként egy)
  3. Editor (Szerkesztő)
  4. ModelWizard (ModellVarázsló)
  5. ActionBarContributor (MűveletiSávSegítő)
  6. Plugin (Bedolgozó)
  7. plugin.xml
  8. Icons (Ikonok)

A szerkesztőnek működnie kell, amint elkészül. El fog indulni, de lehet, hogy nem olyan módon működik, amire számított (azaz a generátor által választott alapértékek nem biztos, hogy a modell számára megfelelőek). Viszonylag könnyű azonban az előállított kódot néhány helyen módosítani úgy, hogy az egy alapvető, működőképes szerkesztőt eredményezzen.

Az alábbiakban közelebbről áttekintünk néhány olyan előállított osztályt, mely nagyobb érdeklődésre tarthat számot.

ItemProviderAdapterFactory (ElemSzolgáltatóIllesztőGyár)

Az előállított ItemProviderAdapterFactory gyár az előállított AdapterFactory osztály egyszerű alosztálya, ami az EMF-modell elkészítésekor keletkezett.

Megjegyzés: Az előállított EMF illesztőgyár az illesztőket úgy készíti el, hogy elindítja az alosztályok (mint például az ItemProviderAdapterFactory) által újradefiniálandó, típus-specifikus create() metódust. Az EMF illesztőgyár (például ABCAdapterFactory - ABCIllesztőGyár) egy másik előállított osztályt (ABCSwitch - ABCKapcsoló) használ az indítás hatékony kivitelezéséhez.

Az Állapotteljes minta használata esetén az illesztőgyár "create" (létrehozás) metódusai az alábbiak szerint adják vissza az új objektumot:

  class ABCItemProviderAdapterFactory extends ABCAdapterFactoryImpl
  {
    ...
    public Adapter createCompanyAdapter()
    {
      return new CompanyItemProvider(this);
    }
    ...
  }

Az Egyke minta használata esetén pedig az illesztőgyár az egyke példányt is nyomon követi, és minden egyes hívásnál visszaadja azt:

  protected DepartmentItemProvider departmentItemProvider;

  public Adapter createDepartmentAdapter()
  {
    if (departmentItemProvider == null)
    {
      departmentItemProvider = new DepartmentItemProvider(this);
    }
    return departmentItemProvider;
  }

Az ItemProvider (ElemSzolgáltató) osztályok

A modell minden egyes osztályára készül egy annak megfelelő elemszolgáltató osztály. Az előállított elemszolgáltatók az összes olyan felületet vegyítik, amelyek szükségesek a szabvány megjelenítők, parancsok és tulajdonságlapok támogatásához:

  public class DepartmentItemProvider extends ...
    implements 
      IEditingDomainItemProvider,
      IStructuredItemContentProvider, 
      ITreeItemContentProvider, 
      IItemLabelProvider, 
      IItemPropertySource
  {
    ...
  }

Ha a modellosztály főosztály (vagyis nincsen kifejezett alaposztálya), akkor az előállított elemszolgáltató az EMF.Edit elemszolgáltató alaposztályától, az ItemProviderAdapter osztálytól végzi el a kiterjesztést:

  public class EmployeeItemProvider extends ItemProviderAdapter ...

Ha a modellosztály azonban egy alaposztály örököse, akkor az előállított elemszolgáltató az alapszintű elemszolgáltatótól végzi el a kiterjesztést az alábbiak szerint:

  public class EmployeeItemProvider extends PersonItemProvider ...

Többszörös öröklés esetén az előállított elemszolgáltató a kiterjesztést az első alaposztály elemszolgáltatójától kezdi (mint az egyszeres öröklés esetén), és a fennmaradó alaposztályokra megvalósítja a szolgáltatói funkciót.

Elég egy pillantást vetni az előállított elemszolgáltatókra, hogy észrevegyük: funkciójuk nagy része valójában az elemszolgáltató alaposztályban valósul meg. Az előállított elemszolgáltató alosztályok legfontosabb funkciói az alábbiak:

  1. Leszármazott és szülő azonosítása
  2. Tulajdonságleírók beállítása
  3. Az "érdekes" értesítési események továbbadása

Editor (Szerkesztő) és ModelWizard (ModellVarázsló)

Az előállított Editor és ModelWizard bemutatják, hogyan kell az összes előállított részt általános JFace összetevők segítségével összerakni ahhoz, hogy működőképes szerkesztőt állítsunk elő.

A ModelWizard segítségével új, a modell típusával egyező erőforrásokat lehet létesíteni. Ha azonban valamilyen más módon már létrehozott egy erőforrást, akkor a ModelWizard teljes mértékben kihagyható, mert az erőforrás - a munkaasztal munkaterületére történő importálást követően - közvetlenül szerkeszthető.



[1] Az EMF keretrendszerben a Notifier (Értesítő) azoknak az objektumoknak az alapszintű felülete, amelyek be tudnak jegyezni illesztőket és értesítést tudnak azoknak küldeni. Kiterjesztését az EObject végzi, mely a modell összes objektumának alapszintű felülete.

[2] Az EMF illesztőgyárait valójában az öröklés irányítja, tehát lehetőség van egy alosztályokat kezelő, a modell bármely szintjén elhelyezkedő alapillesztő használatára - ennek végletes esete az EObject.

[3] A megjelenítők módosítási értesítőjeként működő illesztőgyáron túlmenően az ItemProviderAdapter (ElemSzolgáltatóIllesztő) is rendelkezhet más (közvetlen) figyelőkkel, melyeket ugyancsak az ItemProviderAdapter.fireNotifyChanged() metódus hív meg.