Создание метамоделей при помощи Dynamic EMF

Создайте динамические Ecore-модели по требованию, не генерируя Java-классы реализации

Узнайте, как интегрированная среда Dynamic Eclipse Modeling Framework (EMF) позволяет разработчикам создавать динамические Ecore-модели по требованию, не генерируя Java™-классы реализации. Данная статья познакомит вас с API и покажет, как сериализовать и загрузить динамические Ecore-модели и их экземпляры.

Нидхи Сингх (Nidhi Singh), инженер-программист, IBM India Software Labs

Нидхи Сингх (Nidhi Singh) - фотографияНидхи Сингх (Nidhi Singh) - разработчик в компании Autonomic Computing Technology Centre в IBM India. Она разрабатывает основанные на EMF редакторы при создании компонентов системы времени исполнения Self-Managed Resource (SMR). Работает в IBM более трех лет. Интересуется структурами данных, алгоритмами и дискретной математикой.



Рохит Баббар (Rohit Babbar), инженер-программист, IBM India Software Labs

Рохит Баббар (Rohit Babbar) - фотографияРохит Баббар (Rohit Babbar) работает инженером-программистом в отделе Host Access Client Package IBM группы программного обеспечения Rational. Основными его интересами являются разделы теории компьютерных систем, такие как теория графов и алгоритмы аппроксимации.



11.08.2008

Eclipse Modeling Framework (EMF) описывает модели данных и позволяет легко генерировать исходный код из артефактов модели данных различного типа, например, XML Schema, модели Rational Rose®, Ecore-модели или Java-аннотаций. В процессе генерирования кода EMF-генератор создает исходный код модели, который включает типизированные интерфейсы и классы реализации для модели данных. Однако в некоторых ситуациях эти типизированные интерфейсы и классы реализации не нужны приложению. Вместо них требуются объекты данных, которые могли бы использоваться совместно или обрабатываться в дальнейшем компонентами приложения.

В таких ситуациях выручает Dynamic EMF, поскольку она позволяет разработчикам приложений программно изготовить базовую модель в памяти во время исполнения, динамически создать ее экземпляры и обратиться к элементам экземпляра модели, используя EMF-рефлективные API.

Почему Dynamic EMF

Главная ценность Dynamic EMF состоит в том, что она позволяет создавать Ecore-модели во время исполнения, используя всего лишь несколько строк кода, а затем создавать и обращаться к экземплярам этой динамической модели с самыми разными целями. Создание базовой модели таким способом помогает избежать генерирования интерфейсов и классов реализации в тех случаях, когда они не нужны.

Такая методология создания моделей и экземпляров моделей особенно полезна в сценариях (хотя не только в них), в которых:

  • Не нужны типизированные интерфейсы или классы реализации - только простые объекты данных должны совместно использоваться компонентами приложения. В этом случае генерирование кода модели с использованием генератора кода EMF было бы излишним для приложения, поскольку приводило бы к ненужной поддержке и развертыванию всего набора сгенерированных интерфейсов (классов). Используя Dynamic EMF, можно "на лету" программно создать базовую модель, содержащую динамические классы, и экземпляры этой модели. Экземпляры таких динамических классов затем можно будет использовать для разделения данных или для дальнейшей обработки компонентами приложения.
  • Модель данных известна только во время исполнения - в этом сценарии, поскольку модель данных не известна во время разработки, создание статических моделей посредством генератора кода EMF не было бы хорошим вариантом. В таких случаях динамические базовые модели и их экземпляры, которые можно было бы создавать во время исполнения, более подходили бы под требования приложения.

Создание динамической базовой модели в памяти

Мы начнем с программного создания динамической Ecore-модели, а затем создадим динамические экземпляры модели. Позднее мы увидим, как прочитать и записать значения элементов, имеющихся в экземпляре модели.

Создание динамической Ecore-модели (метамодели)

Чтобы продемонстрировать создание динамической Ecore-модели, мы рассмотрим модель книжного магазина. Для ясности представим модель, используя Unified Modeling Language (UML).

Рисунок 1. Модель BookStore
Рисунок 1. Модель BookStore

Начнем с создания набора элементов базовой модели, включая экземпляр EcoreFactory, экземпляр EcorePackage, два экземпляра EClass и экземпляр EPackage (см. листинг 1).

Листинг 1. Создание элементов базовой модели
/*
* Создать экземпляр EcoreFactory
*/
EcoreFactory theCoreFactory = EcoreFactory.eINSTANCE;

/*
* Создать экземпляр EClass для класса модели BookStore 
*/
EClass bookStoreEClass = theCoreFactory.createEClass();
bookStoreEClass.setName("BookStore");

/*
* Создать экземпляр EClass для класса модели Book 
*/
EClass bookEClass = theCoreFactory.createEClass();
bookEClass.setName("Book");

/*
* Создать экземпляр EPackage и предоставить уникальный URI
* для идентификации этого пакета
*/
EPackage bookStoreEPackage = theCoreFactory.createEPackage();
bookStoreEPackage.setName("BookStorePackage");
bookStoreEPackage.setNsPrefix("bookStore");
bookStoreEPackage.setNsURI("http:///com.ibm.dynamic.example.bookstore.ecore");

EcoreFactory предоставляет методы для создания таких элементов модели как EClass, EAttribute, EPackage и т.д. Используя экземпляр EcoreFactory, мы создадим два экземпляра EClass: один для представления класса BookStore и второй для представления класса Book (как специфицировано в модели BookStore). Затем мы создадим EPackage, куда в конечном итоге будут помещены наши классы BookStore и Book. Затем мы определим имя и атрибуты nsPrefix пакета bookStoreEPackage. Поскольку имя пакета не обязательно должно быть уникальным, необходимо предоставить bookStoreEPackage URI для его уникальной идентификации. Это делается путем установки значения атрибута nsURI, используя метод setNsURI().

Теперь мы создадим атрибуты, как специфицировано в модели данных BookStore, для наших динамических классов. Чтобы установить смоделированные типы данных для каждого атрибута, мы создаем экземпляр EcorePackage, который содержит методы доступа (accessors) для представления метаобъектам каждого типа данных (см. листинг 2).

Листинг 2. Создание атрибутов динамических классов
/*
* Создать экземпляр EcorePackage
*/
EcorePackage theCorePackage = EcorePackage.eINSTANCE;

/*
* Создать атрибуты для класса BookStore, как указано в модели
*/
EAttribute bookStoreOwner = theCoreFactory.createEAttribute();
bookStoreOwner.setName("owner");
bookStoreOwner.setEType(theCorePackage.getEString());
EAttribute bookStoreLocation = theCoreFactory.createEAttribute();
bookStoreLocation.setName("location");
bookStoreLocation.setEType(theCorePackage.getEString());
EReference bookStore_Books = theCoreFactory.createEReference();
bookStore_Books.setName("books");
bookStore_Books.setEType(bookEClass);
bookStore_Books.setUpperBound(EStructuralFeature.UNBOUNDED_MULTIPLICITY);
bookStore_Books.setContainment(true);

/*
* Создать атрибуты для класса Book, как указано в модели
*/
EAttribute bookName = theCoreFactory.createEAttribute();
bookName.setName("name");
bookName.setEType(theCorePackage.getEString());
EAttribute bookISBN = theCoreFactory.createEAttribute();
bookISBN.setName("isbn");
bookISBN.setEType(theCorePackage.getEInt());

Затем необходимо добавить атрибуты каждого класса в список eStructuralFeatures соответствующего класса. И, наконец, мы поместим оба наших класса в динамический пакет bookStoreEPackage. Это завершит процесс создания метамодели для данной модели BookStore.

Листинг 3. Ассоциирование атрибутов с соответствующими классами и помещение классов в динамический пакет
/*
* Добавить атрибуты (ссылки) owner, location и books в класс
* BookStore
*/
bookStoreEClass.getEStructuralFeatures().add(bookStoreOwner);
bookStoreEClass.getEStructuralFeatures().add(bookStoreLocation);
bookStoreEClass.getEStructuralFeatures().add(bookStore_Books);

/*
* Добавить атрибуты name и isbn в класс Book 
*/
bookEClass.getEStructuralFeatures().add(bookName);
bookEClass.getEStructuralFeatures().add(bookISBN);

/*
* Поместить классы BookStore и Book в bookStoreEPackage
*/
bookStoreEPackage.getEClassifiers().add(bookStoreEClass);
bookStoreEPackage.getEClassifiers().add(bookEClass);

Создание экземпляров динамической модели

После создания нашей Ecore-модели в памяти мы можем создать экземпляры динамических классов, представленных в модели. Сначала мы получаем экземпляр EFactory путем активизации метода bookStoreEPackagegetEFactoryInstance().

Листинг 4. Создание динамических экземпляров
/*
* Получить экземпляр EFactory из BookStoreEPackage 
*/
EFactory bookFactoryInstance = bookStoreEPackage.getEFactoryInstance();

/*
* Создать динамические экземпляры BookStoreEClass и BookEClass
*/
EObject bookObject = bookFactoryInstance.create(bookEClass);
EObject bookStoreObject = bookFactoryInstance.create(bookStoreEClass);

Если реализация этой модели была бы сгенерирована генератором кода EMF, она предоставляла бы классы реализации для пакета и фабрику модели. Сгенерированный пакет при инициализации (посредством своего конструктора) регистрирует сгенерированную фабрику и создает экземпляр ссылки eFactoryInstance на сгенерированный класс фабрики. Поэтому активизация метода getEFactoryInstance() сгенерированного пакета возвращала бы соответствующую сгенерированную фабрику. Поскольку в нашей динамической базовой модели нет какой-либо сгенерированной фабрики или сгенерированных классов любого типа, вызов метода bookStoreEPackagegetEFactoryInstance() возвращает экземпляр динамической фабрики EFactoryImpl.

EFactoryImpl определяет операцию create(), которая создает и возвращает новый экземпляр класса модели, указанный в качестве аргумента. В случае сгенерированного кода модели этот метод переопределяется сгенерированной фабрикой для создания и возврата экземпляров соответствующих сгенерированных классов модели. В динамической метамодели, поскольку отсутствуют сгенерированные классы реализации фабрики (модели), активизация метода create() экземпляра EFactory возвращала бы экземпляр EObjectImpl.

Чтение и запись динамической модели

Класс EObjectImpl содержит реализацию всех программных интерфейсов рефлективного API, которые можно использовать для доступа к атрибутам наших динамических классов, что позволяет читать и записывать модель, как показано ниже.

Листинг 5. Получение (установка) значений элементов экземпляра модели
/*
* Установить значения атрибутов bookStoreObject 
*/
bookStoreObject.eSet(bookStoreOwner, "David Brown");
bookStoreObject.eSet(bookStoreLocation, "Street#12, Top Town, NY");
((List) bookStoreObject.eGet(bookStore_Books)).add(bookObject);

/*
* Установить значения атрибутов bookObject 
*/
bookObject.eSet(bookName, "Harry Potter and the Deathly Hallows");
bookObject.eSet(bookISBN, 157221);

/*
* Прочитать (получить) значения атрибутов bookStoreObject 
*/
String strOwner =(String)bookStoreObject.eGet(bookStoreOwner);
String strLocation =(String)bookStoreObject.eGet(bookStoreLocation);

/*
* Прочитать (получить) значения атрибутов bookObject  
*/
String strName =(String)bookObject.eGet(bookName);
Object iISBN = bookObject.eGet(bookISBN);

Аналогичным образом с классами модели при необходимости можно активизировать другие рефлективные API, реализованные в классе EObjectImpl (eIsSet() и eUnSet()).


Сериализация динамической модели

Динамическая модель может быть сериализована при помощи четырех фундаментальных интерфейсов интегрированной среды EMF Persistence framework: Resource, ResourceSet, Resource.Factory и URIConverter. Процедура сериализации будет зависеть от того, хотим ли мы сериализовать модель в той же программе, в которой будем десериализовать ее, или нам нужно сериализовать ее в какой-нибудь другой программе, независимо от программы, в которой будем загружать или десериализовать модель.

Выполните указанные далее действия, если сериализация и десериализация базовой модели должны производиться в одной и той же программе; в противном случае перейдите к листингу 7. Для инициализации процесса сериализации сначала регистрируется фабрика XML-ресурсов для обработки файлов с любым расширением, как показано в листинге 6. Затем, мы создаем пустой ресурс в наборе ресурсов путем активизации метода createResource() экземпляра ResourceSet и передачи реального месторасположения ресурса в виде URI. Мы добавляем наш EObject (bookStoreEObject) в список содержимого этого ресурса и, используя метод save(), копируем ресурс в его персистентное хранилище (при необходимости набором ресурсов может использоваться URIConverter для обнаружения ресурса или для нормализации входного URI в реальный URI ресурса).

Листинг 6. Сериализация экземпляра динамической модели
ResourceSet resourceSet = new ResourceSetImpl();
/*
* Зарегистрировать реализацию XML Factory, используя DEFAULT_EXTENSION
*/
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "*", new  XMLResourceFactoryImpl());

/*
* Создать пустой ресурс с данным URI
*/
Resource resource = resourceSet.createResource(URI.createURI("./bookStore.xml"));

/*
* Добавить bookStoreObject в список содержимого ресурса 
*/
resource.getContents().add(bookStoreObject);

try{
    /*
    * Сохранить ресурс
    */
      resource.save(null);
   }catch (IOException e) {
      e.printStackTrace();
   }

Получающийся сериализованный экземпляр нашей динамической модели будет выглядеть примерно так (рисунок 2):

Рисунок 2. Сериализованный экземпляр динамической модели
Рисунок 2. Сериализованный экземпляр динамической модели

Используйте следующую процедуру, если сериализация и десериализация базовой модели должна выполняться в других, независимых, программах; в противном случае вернитесь к листингу 6. При загрузке динамической модели нам нужно обратиться к динамическому пакету bookStoreEPackage. Если модель должна быть загружена в той же программе, в которой она сериализуется, это можно сделать легко (см. следующий раздел). Но если модель должна загружаться в программе, отличной от той, в которой она сериализуется, нам нужно сначала сериализовать нашу динамическую Ecore-модель перед сериализацией экземпляра модели, для того чтобы можно было обратиться к bookStoreEPackage.

Листинг 7. Сериализация метамодели
ResourceSet metaResourceSet = new ResourceSetImpl();

/*
* Зарегистрировать реализацию XML Factory для обработки .ecore-файлов
*/
metaResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "ecore", new  XMLResourceFactoryImpl());

/*
* Создать пустой ресурс с данным URI
*/
Resource metaResource = \
metaResourceSet.createResource(URI.createURI("./bookStore.ecore"));

/*
* Добавить bookStoreEPackage в список содержимого ресурса  
*/
metaResource.getContents().add(bookStoreEPackage);

try {
     /*
     * Сохранить ресурс
     */
     metaResource.save(null);
    } catch (IOException e) {
      e.printStackTrace();
   }

Сначала мы регистрируем реализацию фабрики XML-ресурсов для обработки файлов с расширением Ecore, поскольку базовая модель будет сериализовываться, используя это расширение. Затем мы создаем пустой ресурс и добавляем наш динамический пакет bookStoreEPackage в список содержимого только что созданного ресурса. Наконец, мы сохраняем этот ресурс.

Полученная сериализованная динамическая базовая модель, или метамодель, выглядит примерно так, как показано на рисунке 3.

Рисунок 3. Сериализованная динамическая базовая модель
Рисунок 3. Сериализованная динамическая базовая модель

Теперь мы сериализуем документ экземпляра модели bookStore.xml. Единственным отличием является то, что в этот раз документ экземпляра сериализуется с дополнительным атрибутом xsi:schemaLocation. Загрузчик будет использовать этот атрибут для поиска сохраненного ресурса bookStore.ecore, содержащего сериализованный EPackage, необходимый для загрузки документа экземпляра модели.

Листинг 8. Сериализация документа экземпляра модели с атрибутом xsi:schemaLocation
ResourceSet resourceSet = new ResourceSetImpl();
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "*", new  XMLResourceFactoryImpl());

Resource resource = resourceSet.createResource(URI.createURI("./bookStore.xml"));
resource.getContents().add(bookStoreObject);

/*
* Сохранить ресурс, используя параметр сохранения OPTION_SCHEMA_LOCATION, 
* для формирования атрибута xsi:schemaLocation в документе
*/
Map options = new HashMap();
options.put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE);
try{
     resource.save(options);
   }catch (IOException e) {
     e.printStackTrace();
   }

Сериализолванный документ экземпляра модели, bookstore.xml, будет иметь атрибут xsi:schemaLocation, как показано ниже.

Рисунок 4. Сериализованный экземпляр модели с атрибутом xsi:SchemaLocation
Рисунок 4. Сериализованный экземпляр модели с атрибутом xsi:SchemaLocation

Десериализация (загрузка) динамической модели

Теперь рассмотрим, как загрузить документ экземпляра динамической модели, который мы только что сериализовали.

Используйте приведенную далее процедуру десериализации, если сериализация и десериализация выполняются в одной и той же программе. Как и в процессе сериализации мы сначала регистрируем реализацию фабрики XML-ресурсов для работы с файлами, имеющими любые расширения. Затем мы добавляем наш динамический bookStoreEPackage в реестр пакетов, используя тот же самый URI пространства имен, который дали при сериализации модели (этот URI имеет вид xmlns:book=http:///com.ibm.dynamic.example.bookstore.ecore в полученном нами документе экземпляра сериализованной модели).

Листинг 9. Загрузка документа экземпляра модели
ResourceSet load_resourceSet = new ResourceSetImpl();

/*
* Зарегистрировать реализацию XML Factory, используя DEFAULT_EXTENSION
*/
load_resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "*", new XMLResourceFactoryImpl());

/*
* Добавить bookStoreEPackage в реестр пакетов
*/
load_resourceSet.getPackageRegistry().put(
    "http:///com.ibm.dynamic.example.bookstore.ecore",
     bookStoreEPackage);

/*
* Загрузить ресурс, используя URI
*/
Resource load_resource = load_resourceSet.getResource(
    URI.createURI("./bookStore.xml"),true);

/*
* Прочитать обратно сериализованные экземпляры динамических классов, сохраненные в 
* ресурсе
*/
EObject readBookStoreObject = (EObject)
    load_resource.getContents().get(0);
EObject readBookObject = 
    (EObject)((EList)(readBookStoreObject.eGet(bookStore_Books))).get(0);

System.out.println("Owner: " + readBookStoreObject.eGet(bookStoreOwner)
    + "\nLocation: " + readBookStoreObject.eGet(bookStoreLocation)
    + "\nBook:\n name: " + readBookObject.eGet(bookName) 
    + "\t isbn: " + readBookObject.eGet(bookISBN));

После регистрации нашего пакета в реестре пакетов мы загружаем ресурс, активизируя метод getResource() экземпляра набора ресурсов. При этом будет загружен документ экземпляра модели с использованием URI, переданным нами в качестве аргумента в метод getResource(). Наконец, мы обращаемся к сериализованным экземплярам в документе, используя reflective API, как показано выше.

Выполните приведенную далее процедуру, если сериализация и десериализация базовой модели выполняется в различных, независимых программах. Здесь процедура загрузки документа экземпляра остается такой же, за исключением того, что не нужно добавлять наш динамический bookStoreEPackage в реестр пакетов ResourceSet. Это происходит потому, что при загрузке нами документа экземпляра bookStore.xml загрузчик будет искать URI пространства имен для отображения URI пакета в атрибуте xsi:schemaLocation документа экземпляра. Используя это отображение, загрузчик будет автоматически загружать сериализованную модель bookStore.ecore, содержащую динамический bookStoreEPackage. После загрузки этого динамического пакета можно обратиться к сериализованным экземплярам обычным способом, используя EMF-рефлективные API, как показано в листинге 9.


Ограничения

Модели, созданные с использованием Dynamic EMF, являются немного более медленными и используют больше пространства по сравнению с моделями, сгенерированными посредством генератора EMF. Это происходит потому, что динамические модели полагаются на динамическую реализацию рефлективных API, обеспечиваемую классом EObjectImpl, для получения и установки динамических функциональных возможностей экземпляра. Например, динамическая модель будет использовать более медленный метод eGet(EstructuralFeature myFeature) вместо более быстрого (сгенерированного) метода getMyFeature(), применяемого сгенерированной базовой моделью. Более того, динамические настройки требуют дополнительного пространства в экземплярах динамической модели, чего не требуется для кода модели, сгенерированного генератором EMF.


Заключение

Вы узнали, как создать Ecore-модели (которые могут быть созданы), используя Dynamic EMF, а также как создавать их экземпляры "на лету", не имея соответствующих Java-классов реализации. Использование такого подхода к созданию моделей становится особенно интересным в сценариях, когда часть кода модели приложения генерируется посредством генератора кода EMF, а остальной код модели создается при помощи Dynamic EMF. В этом и подобных сценариях среда Dynamic EMF (если применяется эффективно) может пройти долгий путь при разрешении приложениям совместно использовать данные рефлективно. В то же время она может уменьшить сгенерированный код реализации, который в противном случае нужно было бы поддерживать в процессе развития модели.


Загрузка

ОписаниеИмяРазмер
Пример кодаos-dynamicemf.sampleDynamicBookstoreModel.zip8KB

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=330143
ArticleTitle=Создание метамоделей при помощи Dynamic EMF
publish-date=08112008