Viimeksi päivitetty: 1. kesäkuuta 2004
Tämän asiakirjan lukijan odotetaan tuntevan EMF (Eclipse Modeling Framework) -kehyksen peruskäsitteet. Lisätietoja EMF:stä on kohdassa EMF:n yleiskuvaus.
Johdanto
EMF-objektien tarkastelu JFace-katseluohjelmissa
Kohteen toimittajan toteutusluokat
EMF-mallien muuttaminen komentojen avulla
EMF.Edit-koodin luontitoiminnon käyttö
Jos käytössä on EMF-perustainen malli, jota varten olet luonut koodin EMF-koodin luontitoiminnolla, ja jos olet valmis lisäämään käyttöliittymiä malliin ja koodiin, EMF.Edit-kehyksen käyttö helpottaa työtä.
EMF.Edit on Eclipse-kehys, joka sisältää yleisiä uudelleen käytettäviä luokkia muokkausohjelmien muodostamiseksi EMF-malleja varten. Se toimittaa seuraavat:
Tässä artikkelissa luodaan yleiskuva EMF.Edit-kehyksen ja luontityökalun peruskäsitteistä. Tarkempia tietoja saat kehysluokkien käyttöohjeista, joissa on esitelty yksityiskohtaisesti, mitä erityistä luokkien toiminnassa ja toiminnoissa on.
Eclipse-käyttöliittymäkehys (JFace) sisältää joukon uudelleen käytettäviä katseluohjelmaluokkia (esimerkiksi TreeViewer ja TableViewer), joilla voidaan näyttää rakenteisia malleja. Ne eivät edellytä, että tarkasteltavat malliobjektit ovat tietyn protokollan mukaisia (tietyn liittymän toteutus ei ole tarpeen). JFace-katseluohjelmat toimivat yhdessä kaikenlaisten objektien kanssa (minkä tahansa java.lang.Object-aliluokan kanssa). Tämä perustuu siihen, että katseluohjelmat eivät siirry malliobjekteihin suoraan, vaan käyttävät niitä sovitinobjektien välityksellä. Näitä sovitinobjekteja kutsutaan sisällön toimittajiksi.
Kaikissa katseluohjelmaluokissa on käytössä sisällön toimittaja, joka ottaa käyttöön tietyn toimittajaliittymän. Esimerkiksi TreeViewer käyttää sisällön toimittajaa, joka ottaa käyttöön seuraavan liittymän:
public interface ITreeContentProvider ... { public Object[] getChildren(Object object); public Object getParent(Object object); ... }
Perusrakenne näkyy seuraavassa kaaviossa:
TreeViewer-katseluohjelma näyttää objektirakenteen (joita se kutsuu kohteiksi) näytössä. Katseluohjelma saa kaikki objektit syöteobjektia (pääobjektia) lukuun ottamatta käyttöön esittämällä getChildren()-kutsun sisällön toimittajalle.
Muunlaisia katseluohjelmia käsitellään vastaavalla tavalla. Katseluohjelma vaatii aina sisällön toimittajan tietyn liittymän käyttöön ottamista varten. Vaikka kaikki katseluohjelmien liittymät ovat erilaisia, sisällön toimittaja voi usein ottaa käyttöön niitä monta kerrallaan. Samaa sisällön toimittajaluokkaa voidaan silloin käyttää erilaisissa katseluohjelmissa.
EMF.Edit-kehys toimittaa yleisen sisällön toimittajaluokan, jota voidaan käyttää sisällön toimittamiseen EMF-malleille. Luokka AdapterFactoryContentProvider toteuttaa sisällön toimittajaliittymät delegoimalla EMF-sovittimille, jotka osaavat siirtyä katseluohjelmien malliobjekteihin (kohteisiin). Esimerkiksi rakenteen katseluohjelman tukemiseen käytetty EMF-sovitinluokka ottaisi käyttöön seuraavan EMF.Edit-liittymän:
public interface ITreeItemProvider { public Collection getChildren(Object object); public Object getParent(Object object); ... }
Tämä liittymä on samankaltainen kuin edellä kuvattu ITreeContentProvider-sisällön toimittajaliittymä. AdapterFactoryContentProvider toteuttaa sisällön toimittajaliittymän paikantamalla sovittimen ja delegoimalla sen jälkeen sovittimelle (toteuttamalla kohteen toimittajaliittymän). Termin vaihto objektista kohteeksi on tarkoituksenmukaista: katseluohjelman kannalta kyse on kohteista, ei objekteista.
EMF-kuva on tämän näköinen:
Huomautus: voit luoda automaattisesti ItemProviderAdapterFactory- ja ItemProvider-luokat tietylle EMF-mallille käyttämällä EMF.Edit-kehyksen mukana tulevaa luontitoimintoa. Aihetta on käsitelty jäljempänä tarkemmin.
AdapterFactoryContentProvider on luotu sovittimen factorylla, joka auttaa muiden EMF-sovittimien factoryiden tavoin luomaan ja paikallistamaan tietyntyyppisiä sovittimia (tässä tapauksessa ItemProvider-tyyppisiä). Sisällön toimittaja täyttää getChildren()-pyynnön kaltaiset pyynnöt esittämällä (kohteen) mukautuskutsun ItemProviderAdapterFactory-factorylla, joka luo tai palauttaa ItemProvider-sovittimen määritetylle kohteelle. Sen jälkeen se yksinkertaisesti delegoi vaaditun liittymän (ITreeItemProvider-liittymän tässä tapauksessa) getChildren()-menetelmään.
Kyseinen getChildren()-menetelmä näyttää AdapterFactoryContentProvider-sisällön toimittajassa seuraavankaltaiselta:
public Object[] getChildren(Object object) { ITreeItemContentProvider adapter = (ITreeItemContentProvider) adapterFactory.adapt(object, ITreeItemContentProvider.class); return adapter.getChildren(object).toArray(); }
Tätä samaa kaavaa käytetään kaikissa sisällön toimituksen menetelmissä. Aiemmin todetulla tavalla AdapterFactoryContentProvider yksinkertaisesti vain delegoi sisällön toimittajamenetelmät tiettyyn kohteen toimittajaan (sovittimeen), joka osaa täyttää pyynnön.
Edellä esitetyssä getChildren()-menetelmässä objekti, joka siirretään adapterFactory.adapt()-sovittimeen, on yksinkertainen java.lang.Object (ei org.eclipse.emf.ecore.EObject). Tämä on tärkeä EMF.Edit-kehyksen ominaisuus. Kehys on suunniteltu tarkasti mukauttamaan EMF-mallien näkymiä, jotka voivat poiketa varsinaisesta mallista (näkymät joko estävät objekteja tai sisältävät lisä- eli haamuobjekteja). Jotta EMF-objektien ja muiden kuin EMF-objektien yhdistelmä olisi mahdollinen, kehyksen perusluokka sovittimen factoryille toteuttaa sovituksen(). Tämä toimii seuraavan kaltaisesti:
public Object adapt(Object object, Object type) { if (object instanceof Notifier) return this.adapt((Notifier)object, type); else return object; }
Jos määritetty objekti ei ole EMF-ilmoitustoiminto [1] , se palauttaa varsinaisen objektin. Rakenteen ansiosta kohteen toimittaja, joka haluaa lisätä muita kuin EMF-kohtia näkymään, voi yksinkertaisesti palauttaa (esimerkiksi getChildren()-menetelmästä) minkä tahansa objektin, joka ei ole EMF-objekti. Jos palautettu objekti toteuttaa vaaditun katseluohjelman kohteen toimittajaliittymän (esimerkiksi ITreeItemProvider-liittymän), sitä käsitellään muiden EMF-kohteiden tavoin.
Rakenteen tämä puoli korostaa sitä, miksi kutsumme toimittaja-/sovitinluokkia mieluummin kohteen toimittajiksi kuin sovittimiksi. Joissakin tärkeissä sovelluksissa katselumalli (jonka katseluohjelman sisällön toimittaja on toimittanut) on usein yhdistelmä "todellisia" (EMF) malliobjekteja, joiden kohteen toimittajat ovat myös (EMF) sovittimia, ja "haamuobjekteja", joiden kohteen toimittajat ovat itse objekteja. Kaikki sovittimet ovat samalla myös kohteen toimittajia. Sama ei kuitenkaan välttämättä päde käänteisenä.
Edellisissä osissa kuvasimme, miten JFace-katseluohjelmat käyttävät sisällön toimittajaa sisältökohteiden hankinnassa. Vastaavalla tavalla hankitaan myös nimiön kuva ja teksti kohteille, jotka katseluohjelma näyttää. Katseluohjelma ei kysy kohteilta itseltään niiden nimiöitä, vaan käyttää nimiön toimittajaksi (vastaava kuin sisällön toimittaja) kutsuttua objektia. Esimerkiksi TreeViewer delegoi objektiin toteuttamalla ILabelProvider-liittymän, kun se noutaa kohteiden nimiöt rakenteesta.
EMF.Edit-kehys toteuttaa EMF-mallien nimiön toimittajat käyttämällä samaa mekanismia kuin se käyttää sisällön toimittamiseen. Yleinen nimiön toimittajan toteutusluokka AdapterFactoryLabelProvider (joka toimii täysin samalla tavalla kuin AdapterFactoryContentProvider) delegoi ILabelProvider-liittymän mallin kohteen toimittajille (sama kohteen toimittaja toimittavat myös sisällön). Laajennettu kuva on seuraavan kaltainen:
Sisällön ja nimiön toimittaja voi delegoida (ja tavallisesti tekee niin) samaan sovittimen factoryyn ja siten myös samoihin kohteen toimittajiin. Sisällön toimittajaa ja kohteen toimittajaa yhdistää se, että niissä työ todella tapahtuu.
Käyttäjä voi luoda EMF.Edit-toimittajaluokkien avulla TreeViewer-katseluohjelman joillekin EMF-malleille seuraavasti:
myAdapterFactory = ... treeViewer = new TreeViewer(); treeViewer.setContentProvider(new AdapterFactoryContentProvider(myAdapterFactory)); treeViewer.setLabelProvider(new AdapterFactoryLabelProvider(myAdapterFactory));
Luotu TreeViewer voidaan sen jälkeen näyttää esimerkiksi muokkausohjelman ikkunassa tavalliseen (JFace-katseluohjelmien määritysten mukaiseen) tapaan.
Saatat pitää kaikkea tässä kerrottua asiaa melko itsestään selvänä. Olemme kuitenkin näyttäneet vain sen, miten delegoidaan toiseen (toisin sanoen sovittimen factoryn). Emme ole vielä toteuttaneet yhtäkään menetelmää, vaan pelkästään delegoineet ne pois. Menetelmän toteutusta tukee EMF.Edit, joka sisältää koodin luontitoiminnon. Se luo puolestasi suurimman osan kohteen toimittajan ja factoryn koodista. Tarkastelemme ennen siihen siirtymistä, miten kohteen toimittajat toimivat.
Edellisessä osassa esitetyllä tavalla malliin liittyvät kohteen toimittajan sovittimet tekevät todellisen työn sisällön toimittamiseksi EFM-malleille. Edellisessä kaaviossa ItemProvider-sovittimien määrää ja tyyppejä ei tarkoituksellisesti ilmoitettu tarkkaan. Tämä perustuu siihen, että EMF.Edit-kehys tukee kahta erilaista kaavaa kohteen toimittajan sovittimille:
Tietyn mallin kohteen toimittajat voidaan toteuttaa kummalla tahansa näistä kaavoista tai niiden yhdistelmällä.
Ensimmäisessä kaavassa kaikki mallin objektit vastaavat yksi yhteen -sovitinta. Kaikilla sovittimilla on osoitin (jota kutsutaan kohteeksi) siihen yhteen ainoaan objektiin, jonka sovitin sovittaa.
Kuva on tämän näköinen:
Tämä kaava kaksinkertaistaa esitetyllä tavalla objektien määrän sovelluksessa. Sitä kannattaa siten käyttää vain sovelluksissa, joissa ilmentymiä tarvitaan lisätiloja varten. Sen vuoksi tätä kaavaa kutsutaan tilalliseksi kaavaksi.
Parempi menetelmä, jolla ehkäistään suurin osa ylimääräisistä objekteista, on Singleton-kaava. Tämän kaavan avulla käytetään yhtä kohteen toimittajan sovitinta kaikissa samantyyppisissä kohteissa. Se näyttää tältä:
Tässä kuvassa objekteilla on tavalliseen tapaan sovitinosoittimet, mutta kohteen toimittajilla (jotka ovat yhteisiä) ei ole osoittimia takaisin objekteihin. Saatoit havaita rakennekohteen toimittajaliittymässä, jota käsiteltiin aiemmin sisällön toimittajasta kertovassa osassa, että kaikki menetelmät vaativat lisäargumentin (objektin):
public interface ITreeItemProvider { public Collection getChildren(Object object); public Object getParent(Object object); ... }
Objektiargumentti lisättiin kaikkiin kohteen toimittajaliittymiin erityisesti tukemaan tätä kaavaa. Tilallisessa tapauksessa tämä objekti on aina sama kuin sovittimen osoitin.
Saatat ihmetellä, miksi emme tue "aitoa" Singleton-sovitinkaavaa eli yhtä sovitinta kaikissa objekteissa? Vastauksena on yksinkertaisesti se, että vaikka kaava on yksi mahdollinen (yhteensopiva EMF.Edit-kehyksen kanssa)[2] , emme suosittele sen käyttöä. Täysin dynaaminen toteutus on yksinkertainen, mutta sen mukauttaminen on vaikeaa (ilman limittäisiä tarkistusten ilmentymiä ()). Vaihtoehtona ovat kirjoitetut kohteen toimittajaluokat, joiden periytymishierarkia on kahdennus mallin periytymishierarkiasta. Ne ovat kätevä välityspiste hienon ja puhtaan oliokeskeisen näkymäkoodin toteutuksessa mallille.
Olemme toistaiseksi näyttäneet vain sen, miten voimme tarkastella EMF-malleja käyttämällä sisällön ja nimiön toimittajia. EMF.Edit-kehyksen ominaisuuksia on myös se, että se tukee komentoperustaista mallin muokkausta. Käytämme termiä "muokkaus" toteuttamiskelvottomasta muunnoksesta. Muokkaus on vastakkainen toiminto mallin yksinkertaiselle "kirjoitukselle".
EMF.Edit-liittymää EditingDomain käytetään tuottamaan EMF-mallin muokkausoikeudet. Toinen EMF.Edit-toteutusluokka, AdapterFactoryEditingDomain, toimii sisällön ja nimiön toimittajien tavoin delegoimalla toteutuksen kohteen toimittajille (käyttämällä apuna ItemProviderAdapterFactory-factorya):
Kuvassa esitetyllä tavalla se tuottaa myös oikeudet käyttää komentopinoa, jolla kaikki muunnokset malliin tehdään. Muokkausverkkoalue toimittaa kaksi peruspalvelua:
Muokkausverkkoaluetta voi ehkä parhaiten ajatella mallin muunnoksen tai kirjoituksen toimittajana. Sisällön ja nimiön toimittajat ovat puolestaan katselu- tai lukutoimittajia. Seuraavassa on laajempi kuva:
Käydään läpi yksinkertainen esimerkki mallin muutoksesta.
Yhtiö-luokalla on esimerkissä yhdeltä monelle -viittaus, jonka nimi on osastot, Osasto-luokkaan. Osasto voidaan poistaa yhtiöstä (ja esimerkiksi muokkausohjelman poistotoiminto voidaan toteuttaa) yksinkertaisesti kirjoittamalla seuraava koodi:
Department d = ... Company c = ... c.getDepartments().remove(d);
Koodi on yksinkertainen. Se ei kuitenkaan tee muuta kuin muunnoksen.
Jos osaston poistamisessa käytetään sen sijaan EMF.Edit-kehyksen poistamiskomentoa (org.eclipse.emf.edit.command.RemoveCommand), kirjoitetaan seuraava:
Department d = ... Company c = ... EditingDomain ed = ... RemoveCommand cmd = new RemoveCommand(ed, c, CompanyPackage.eINSTANCE.getCompany_Departments(), d); ed.getCommandStack().execute(cmd);
Osaston poistamisella tällä tavoin on useita etuja:
Komentojen tällainen laaja käyttö on tapa ottaa käyttöön kaikenlaiset toiminnot, jotka EMF.Edit-kehys mahdollistaa sinulle.
Edellisessä esimerkissä loimme RemoveCommand-komennon kättämällä yksinkertaista uutta kutsua. Se toimi hyvin, mutta sitä ei voi kovin helposti käyttää uudelleen; koodifragmentti tekee hyvin erikoisen asian ja poistaa osaston yhtiöstä. Jos sen sijaan haluamme kirjoittaa esimerkiksi uudelleen käytettävän poistotoiminnon, jolla voidaan poistaa millainen objekti tahansa, voimme käyttää tässä apuna EditingDomain-liittymää.
EditingDomain-liittymä sisältää (muun muassa) komentojen factory-menetelmän, createCommand(), jota voidaan käyttää luomaan uuden kutsun asemesta komentoja:
public interface EditingDomain { ... Command createCommand(Class commandClass, CommandParameter commandParameter); ... }
Tämän menetelmän käyttö komennon luomisessa edellyttää, että luot ensin CommandParameter-objektin, määrität komentoparametrit siihen ja kutsut sen jälkeen luontimenetelmän, jolle välität halutun komentoluokan (esimerkiksi RemoveCommand.class-luokan) ja parametrit.
Sen sijaan, että työasemat kävisivät läpi kaiken tämän, sovellamme käytäntöä, joka toimittaa staattiset yksinkertaiset luontimenetelmät() kaikissa komentoluokissa. Staattista luontimenetelmää() käyttämällä voit luoda ja ajaa seuraavan näköisen RemoveCommand-komennon:
Department d = ... EditingDomain ed = ... Command cmd = RemoveCommand.create(ed, d); ed.getCommandStack().execute(cmd);
Kuten huomaat, kyseessä on vain pieni syntaktinen muutos (RemoveCommand.create() sen sijaan, että käytössä olisi uusi RemoveCommand). Erot ovat kuitenkin perustavaa laatua. Välitimme vain yhden argumentin (poistetun objektin) syrjään muokkausverkkoalueesta, kun aikaisemmin argumentteja oli kolme. Tätä koodia voidaan nyt käyttää millaisen tahansa objektin poistamiseen. Delegoimalla komennon luomisen muokkausverkkoalueeseen sallimme sen täyttää puuttuvat argumentit.
Jotta ymmärrämme, miten kaikki tämä toimii, käymme läpi RemoveCommand.create()-kutsun. Aiemmin esitetyllä tavalla staattinen luontimenetelmä() on vain helpoutta lisäävä menetelmä, joka delegoi muokkausverkkoalueeseen seuraavan näköisesti:
public static Command create(EditingDomain domain, Object value) { return domain.createCommand( RemoveCommand.class, new CommandParameter(null, null, Collections.singleton(value))); }
AdapterFactoryEditingDomain ottaa sen jälkeen pyynnön ja välittää sen kohteen toimittajalle käyttämällä normaalia delgointimallia (kuten AdapterFactorContentProvider delegoi aiemmin getChildren()-pyynnön):
public Command createCommand(Class commandClass, CommandParameter commandParameter) { Object owner = ... // get the owner object for the command IEditingDomainItemProvider adapter = (IEditingDomainItemProvider) adapterFactory.adapt(owner, IEditingDomainItemProvider.class); return adapter.createCommand(owner, this, commandClass, commandParameter); }
Huomautus: tarkasteltaessa todellista createCommand()-menetelmää huomataan, että se on todellisuudessa huomattavasti tätä monimutkaisempi. Syynä on se, että menetelmä on suunniteltu muun muassa käsittelemään kerralla tehdyn objektien kokoelman poiston. Menetelmä ei kuitenkaan periaatteessa tee muuta.
CreateCommand()-menetelmä käyttää omistajan objektia käyttääkseen kohteen toimittajaa delegoinnissa (toisin sanoen omistajaa käytetään adapterFactory.adapt()-kutsussa). Esimerkissä omistaja on yhtiö-bjekti (eli siis poistetun osaston pääobjekti). Muokkausverkkoalue määrittää omistajan kutsumalla getParent()-menetelmän poistettavan objektin kohteen toimittajalla.
Kaiken tämän vaikutuksesta poistettavan objektin pääobjektin kohteen toimittajalla kutsutaan lopuksi createCommand-menetelmä (eli yhtiön c CompanyItemProvider alkuperäisessä koodifragmentissa). Näin CompanyItemProvider voi toteuttaa createCommand()-menetelmän toimimalla seuraavan näköisesti:
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()); } ... } }
Työ saadaan näin tehtyä, mutta siihen on olemassa parempikin tapa.
Jokainen kohteen toimittajaluokka (myös EMF-sovitin) on laajennus yksinkertaisesta EMF.Edit-perusluokasta ItemProviderAdapter, joka toimittaa oletusarvon mukaisen toteutuksen muun muassa createCommand()-menetelmästä. Se toteuttaa createCommand()-menetelmän kaikille EMF.Edit-kehyksen toimittamille vakiokomennoille kutsumalla muutaman yksinkertaisen menetelmän (joita käytetään myös muihin kuin vain tähän tarkoitukseen), jotka toteutetaan kohteen toimittajan alaluokissa. Tämä on esimerkki mallimenetelmän määrityskaavasta.
Jotta RemoveCommand-esimerkki toimisi, CompanyItemProvider-toimittajan on toteutettava vain seuraava menetelmä:
public Collection getChildrenFeatures(Object object) { return Collections.singleton(CompanyPackage.eINSTANCE.getCompany_Departments()); }
Esitetyllä tavalla menetelmä palauttaa vähintään yhden ominaisuuden (tässä tapauksessa vain viitteen osastoihin), jota käytetään viittauksessa objektin alielementtiin. Menetelmän kutsumisen jälkeen createCommand()-menetelmän oletusarvon mukainen toteutus selvittää käytettävän ominaisuuden (jos niitä palautetaan useita) ja luo RemoveCommand-komennon oikean ominaisuuden avulla.
Komentojen luonnilla muokkausverkkoalueen avulla on myös se etu, että voimme lisätä eri aliluokkia tai vakiokomentojen täysin erilaisia toteutuksia ja valita ne yksinkertaisesti normaaleilla muokkausohjelmilla. Voimme esimerkiksi haluta tehdä joitakin ylimääräisiä puhdistustoimia aina, kun poistamme osaston yhtiöstä. Helpoin tapa voisi silloin olla luoda RemoveCommand-luokan aliluokka nimeltä RemoveDepartmentCommand seuraavasti:
public class RemoveDepartmentCommand extends RemoveCommand { public void execute() { super.execute(); // do extra stuff ... } }
Tämä oli melko helppoa.
Jos muokkausohjelmassa on käytössä staattinen RemoveCommand.create()-menetelmä (joka kutsuu editingDomain.createCommand()-luokan) uuden RemoveCommand()-luokan asemesta, voimme helposti korvata normaalin RemoveCommand-luokan RemoveDepartmentCommand-luokan ohittamalla createCommand()-luokan kohteen toimittajassa seuraavasti:
public class CompanyItemProvider ... { ... public Command createCommand(final Object object, ...) { if (commandClass == RemoveCommand.class) { return new RemoveDepartmentCommand(...); } return super.createCommand(...); } }
Jos komento, jonka haluamme erikoistaa, on ennalta määritetty (kuten RemoveCommand), korvaus on vieläkin helpompi tehdä. CreateCommand()-luokan oletusarvon mukainen toteutus lähettää kaikkien komentojen luonnin yksinkertaisiin komentokohtaisiin menetelmiin seuraavasti:
public Command createCommand(final Object object, ... { ... if (commandClass == RemoveCommand.class) return createRemoveCommand(...); else if (commandClass == AddCommand.class) return createAddCommand(...); else ... }
Olisimme voineet luoda RemoveDepartmentCommand-luokan helpommin pelkästään ohittamalla createRemoveCommand()-luokan createCommand()-menetelmän itsensä asemesta:
protected Command createRemoveCommand(...) { return new RemoveDepartmentCommand(...); }
Yhteenvetona keskeistä on, että muokkausverkkoalue on ripustin, jolla voimme säätää komentoparametreja, myös itse komentoluokkaa. Voimme siten helposti ohjata kaikkien muokkauskomentojen toimintaa mallissamme.
Emme ole vielä tarkastelleet ilmoitusta, joka annetaan muutoksesta. Miten saamme katseluohjelmat tekemään päivityksen sen jälkeen, kun komento muuttaa jotakin mallissa? Voimme käyttää EMF-sovittimen normaalia ilmoitusta yhdessä EMF.Edit-kehyksen toimittaman katseluohjelman päivitysmekanismin kanssa.
Muodostamisen yhteydessä AdapterFactoryContentProvider rekisteröi itsensä kuuntelutoiminnoksi (kuten org.eclipse.emf.edit.provider.INotifyChangedListener) sovittimen factory-menetelmässään (joka toteuttaa org.eclipse.emf.edit.provider.IChangeNotifier-liittymän). Sovittimen factory puolestaan välittää itsensä kaikille kohteentoimittajille, jotka se luo. Se voi siten olla keskeinen muutoksien ilmoitustoiminto mallille. AdapterFactoryContentProvider myös rekisteröi (inputChanged()-menetelmässä) katseluohjelman, jolle se toimittaa sisältöä. Siten se voi päivittää katseluohjelman muutosilmoituksen vastaanoton jälkeen.
Seuraavassa kaaviossa on kuvattu, miten muutos EMF-malliobjektissa (esimerkiksi yhtiön nimen muutos) siirtyy sovittimen factorysta takaisin mallien katseluohjelmiin.
Aina kun EMF-objektin tila muuttuu, notifyChanged()-menetelmä kutsutaan kaikilla objektin sovittimilla, kuten kohteen toimittajilla (joka on tässä tapauksesa CompanyItemProvider). NotifyChanged()-menetelmä vastaa kohteen toimittajassa sen määrittämisestä, välitetäänkö kukin tapahtumailmoitus katseluohjelmaan ja jos välitetään, minkätyyppinen päivitys pitäisi olla tuloksena.
Tätä varten se kierrättää kiinnostavia ilmoituksia ViewerNotification-liittymässä, joka on yksinkertainen toteutus IViewerNotification-liittymästä. Liittymä on perusilmoitusliittymän seuraavan näköinen laajennus:
public interface IViewerNotification extends Notification { Object getElement(); boolean isContentRefresh(); boolean isLabelUpdate(); }
Nämä menetelmät määrittävät, mikä kohde päivitetään katseluohjelmassa, päivitetäänkö sisältö kyseisen elementin kohdalla ja päivitetäänkö elementin nimiö. Kohteen toimittaja määrittää objektin alielementin ja nimiön, joten sen on määritettävä myös se, miten katseluohjelma päivitetään tehokkaasti.
NotifyChanged()-menetelmä näyttää CompanyItemProvider-luokassa tältä:
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); }
Tässä toteutuksessa nimen määritteen muutos aiheuttaa nimiön päivityksen ja osastoviitteen muutos aiheuttaa sisällön päivityksen. Muilla muutosilmoituksilla ei ole vaikutusta katseluohjelmaan.
FireNotifyChanged()-menetelmä on yksinkertainen menetelmä ItemProviderAdapter-luokassa(kaikkien kohteen toimittajan sovittimien perusluokka), joka yksinkertaisesti välittää ilmoituksen eteenpäin sovittimen factoryyn[3] . Sovittimen factory (muutoksen ilmoitustoiminto) jatkaa ilmoituksen lähettämistä kaikille kuuntelutoiminnoilleen (esimerkissä vain rakenteen katseluohjelman sisällön toimittajalle). Sisällön toimittaja päivittää lopuksi katselutoiminnon ilmoituksen ohjaamalla tavalla.
EMF-mallit on usein sidottu yhteen sekamalliviitteillä. Kun sinun on muodostettava objektien muokkaamista tai näyttämistä varten sovellus, jonka laajuuteen kuuluu enemmän kuin yksi EMF-malli, tarvitset aina sovittimen factoryn, joka pystyy mukauttamaan kahden (tai useamman) mallin objektien yhdisteen.
Usein sinulla on jo valmiina sovittimen factoryita yksittäisiä malleja varten. Silloin sinun tarvitsee vain liimata ne yhteen. Tarkoitukseen voidaan käyttää toista yksinkertaista EMF.Edit-luokkaa, ComposedAdapterFactory-factorya:
ComposedAdapterFactory-menetelmää käytetään toimittamaan yhteinen liittymä muille sovittimen factoryille, joille se yksinkertaisesti delegoi toteutuksen.
Voit asentaa koostetun sovittimen factoryn seuraavasti:
model1AdapterFactory = ... model2AdapterFactory = ... ComposedAdapterFactory myAdapterFactory = new ComposedAdapterFactory(); myAdapterFactory.addAdapterFactory(model1AdapterFactory); myAdapterFActory.addAdapterFActory(model2AdapterFactory); myContentProvider = new AdapterFactoryContentProvider(myAdapterFactory); ...
Huomautus: vaiheittainen opetusohjelma EMF-mallin ja EMF.Edit-muokkausohjelman luomisesta on kohdassa Opetusohjelma: EMF-mallin luonti.
EMF-mallin määritelmän antamisen jälkeen EMF.Edit-koodinluontitoiminto voi tuottaa täysin toimivan muokkaustyökalun. Sillä voit näyttää mallin ilmentymät käyttämällä useita yhteisiä katseluohjelmia ja lisätä, poistaa, leikata, kopioida ja liittää malliobjekteja tai muokata objekteja normaalissa ominaisuustaulukossa. Kaikkea tätä tukevat täydet kumoamis-/uusimistoiminnot.
EMF.Edit-luontitoiminto tuottaa täydellisesti toimivat lisäosat, joihin sisältyvät seuraavat:
Kehittämisen jälkeen järjestelmän pitäisi ajaa muokkausohjelma. Se ei ehkä toimi odottamallasi tavalla (luontitoiminnon tekemät oletusarvoiset valinnat eivät ehkä ole sopivia mallillesi). Luodun koodin optimoimisen pitäisi kuitenkin onnistua melko helposti muutamassa kohdassa. Toimiva perusmuokkausohjelma on siten nopeasti valmis ja toiminnassa.
Seuraavassa käsitellään lähemmin luoduista luokista kiinnostavimpia.
Luotu ItemProviderAdapterFactory-luokka on EMF-mallin luonnin yhteydessä luodun AdapterFactory-luokan yksinkertainen aliluokka.
Huomautus: luotu EMF-sovittimen factory luo sovittimia antamalla vuoron tyyppikohtaiselle luontimenetelmälle(), joka aliluokkien (kuten ItemProviderAdapterFactory-luokan) on ohitettava. EMF-sovittimen factory-menetelmä (esimerkiksi ABCAdapterFactory) käyttää toista luotua luokkaa (ABCSwitch-luokkaa) vuoronvaihdon tehokkaaseen toteutukseen.
Kun käytössä on tilallinen kaava, sovittimen factoryn luomismenetelmät yksinkertaisesti palauttavat uuden objektin seuraavan näköisesti:
class ABCItemProviderAdapterFactory extends ABCAdapterFactoryImpl { ... public Adapter createCompanyAdapter() { return new CompanyItemProvider(this); } ... }
Jos sen sijaan käytössä on Singleton-kaava, sovittimen factory lisäksi seuraa singleton-ilmentymää ja palauttaa sen jokaiselle kutsulle:
protected DepartmentItemProvider departmentItemProvider; public Adapter createDepartmentAdapter() { if (departmentItemProvider == null) { departmentItemProvider = new DepartmentItemProvider(this); } return departmentItemProvider; }
Kaikkia mallin luokkia varten luodaan vastaava kohteen toimittajan luokka. Luodussa kohteen toimittajassa yhdistyvät kaikki liittymät, joita tarvitaan normaalien katseluohjelmien, komentojen ja ominaisuustaulukoiden tukemiseen:
public class DepartmentItemProvider extends ... implements IEditingDomainItemProvider, IStructuredItemContentProvider, ITreeItemContentProvider, IItemLabelProvider, IItemPropertySource { ... }
Jos malliluokka on juuressa (sillä ei ole erikseen määritettyä perusluokkaa), luotu kohteen toimittaja laajenee EMF.Edit-kohteen toimittajan perusluokasta ItemProviderAdapter:
public class EmployeeItemProvider extends ItemProviderAdapter ...
Jos sen sijaan malliluokka periytyy perusluokasta, luotu kohteen toimittaja laajenee kohteen perustoimittajasta seuraavan näköisesti:
public class EmployeeItemProvider extends PersonItemProvider ...
Jos periytyvä luokka on monentuva, luotu kohteen toimittaja laajenee ensimmäisen perusluokan kohteen toimittajasta (kuten yksittäisessä periytyvässä luokassa) ja toteuttaa toimittajatoiminnon jäljellä olevissa perusluokissa.
Jos tarkastelet luotuja kohteen toimittajan luokkia, havaitset, että suuri osa niiden toiminnoista toteutuu oikeasti kohteen toimittajan perusluokassa. Luotujen kohteen toimittaja-aliluokkien tärkeimmät toiminnot ovat seuraavat:
Luotu muokkausohjelma ja ModelWizard näyttävät, miten voit yhdistää kaikki muut luodut osat käyttämällä normaaleja JFace-komponentteja toimivan muokkausohjelman tuottamiseksi.
Ohjattua ModelWizard-toimintoa voi käyttää mallin tyyppiä vastaavien uusien resurssien luomiseen. Jos sinulla jo on jollakin muulla tavalla luotu resurssi, voit tuoda sen työaseman työtilaan ja käynnistää muokkausohjelman työasemassa. Näin voit ohittaa ModelWizard-toiminnon kokonaan.
[1] Ilmoitustoiminto on EMF-kehyksessä perusliittymä objekteille, jotka voivat rekisteröidä sovittimia ja lähettää ilmoituksia niihin. Sen laajennus on EObject, joka on kaikkien malliobjektien perusliittymä.
[2] EMF-sovittimen factory-menetelmät ovat itse asiassa periytyviä, joten voit käyttää perussovitinta aliluokkien käsittelemiseen kaikilla tasoilla mallissa. EObject on tästä ääritapaus.
[3] Sovittimen factory-menetelmä toimii muutoksen ilmoitustoimintona katseluohjelmille. Sen lisäksi ItemProviderAdapter-sovittimella voi olla myös muita (suoria) kuuntelutoimintoja, jotka myös kutsutaan ItemProviderAdapter.fireNotifyChanged()-luokassa.