Eclipse Modeling Framework (EMF) - Обзор

Обновлен: 1 июня 2004 года

В этом документе содержатся краткие сведения о Среде разработки Eclipse (EMF) и приводятся примеры шаблонов, создаваемых генератором кода EMF. Полное описание всех функций EMF можно найти в книге Eclipse Modeling Framework (Addison Wesley, 2003 год) или в документации по самим классам этой среды разработки.

Введение

EMF представляет собой среду Java и средство генерации кода для создания инструментов и других приложений на основе структурированной модели. Пользователям, занимающимся объектно-ориентированным моделированием, EMF предоставляет возможность быстро включать их модели в эффективный, правильный и легко настраиваемый код Java. Для разработчиков, перед которыми не стоит необходимость использовать формальные модели, EMF позволяет получить те же самые преимущества при меньших затратах.

Итак, что означает термин "моделирование"? Говоря о моделировании, мы в общем случае представляем диаграммы классов, диаграммы состояний и т.д. Унифицированный язык моделирования (UML) определяет стандартную запись для объектов такого рода. Комбинация диаграмм UML позволяет определить полную модель приложения. Эту модель можно применять только для документирования или, при наличии подходящих инструментов, в качестве входа для генерации части или (в простейших случаях) всего приложения.

Известно, что моделирование такого рода требует дорогостоящих инструментов объектно-ориентированного анализа и проектирования (OOA/D), поэтому может возникнуть вопрос, каким образом в EMF обеспечивается низкая стоимость записи. Причина заключается в том, что для создания моделей EMF требуется только малое подмножество типов объектов, моделируемых с помощью языка UML, а именно - простые определения классов, их атрибутов и связей, для которых не нужны средства полномасштабного графического моделирования.

В EMF в качестве канонической формы определения моделей применяется XMI (XML Metadata Interchange - Обмен метаданными XML)[1] . Поместить модель в эту форму можно несколькими способами:

Первый метод - самый прямой, но в общем случае он подходит только специалистам по XML. Второй вариант больше подходит тем, кто уже работает со средствами полномасштабного моделирования. Третий вариант предназначен для программистов, пишущих программы на языке Java: он предлагает экономичный способ использования возможностей EMF и генератора кодов прямо базовой среде разработки Java (например, в Java Development Tools программы Eclipse). Последний вариант наиболее подходит для создания приложений, которые должны считывать и сохранять файлы в специальном формате XML.

Как только вы задали модель EMF, для нее можно запустить генератор кода EMF для создания соответствующего набора классов реализации Java. Эти классы можно изменять, добавляя в них методы и переменные экземпляра класса, и при необходимости повторно генерировать их из модели; при этом внесенные добавления останутся неизменными. Если добавляемый вами код зависит от чего-то, что вы меняете в модели, то его по-прежнему требуется обновлять, чтобы учесть внесенные в модель изменения. Во всем остальном ваш код полностью независим от изменений модели и остается неизменным при повторной генерации.

Кроме простого повышения производительности, применение среды EMF для создания приложений позволяет использовать такие ее возможности, как: уведомление об изменении модели, постоянная поддержка, включающая XMI по умолчанию и сериализацию на основе схемы XML, среда для проверки модели, и, наконец, высокоэффективный рефлективный API для работы с объектами EMF. Самое важное преимущество заключается в том, что EMF предоставляет среду для взаимодействия с другими инструментами и приложениями, созданными на основе EMF.

EMF включает две основные среды: базовую среду и среду EMF.Edit. Базовая среда предоставляет средства генерации и динамической поддержки классов реализации Java для модели. EMF.Edit представляет собой расширение и надстройку базовой среды за счет добавления поддержки генерации классов адаптеров. В этой среде можно просматривать модель и редактировать ее в режиме симуляции команд, и даже модель, выполнять ее редактирование в режиме симуляции команд, и даже использовать для рабочей модели стандартный редактор. Основные возможности базовой среды EMF описаны в следующих разделах. Описание среды EMF.Edit содержится в отдельном документе Среда EMF.Edit - Обзор. Инструкции по запуску генератора EMF и EMF.Edit приведены в разделе Справочник: Создание модели EMF.

Связь EMF с MOF OMG

Те, кто знаком с MOF (Meta Object Facility) OMG (Object Management Group - рабочая группа по развитию стандартов объектного программирования), может по достоинству оценить связь EMF с этим инструментом. Фактически, среда EMF первоначально создавалась как реализация спецификации MOF, но по мере накопления опыта по реализации с ее помощью большого набора инструментов вышла за эти рамки. Среду EMF можно рассматривать как высокоэффективную реализацию Java базового подмножества API MOF. Однако, во избежание путаницы базовая метамодель MOF в среде EMF называется Ecore.

В текущей версии MOF 2.0 выделяется подобное подмножество модели MOF, называемое EMOF (Essential MOF). Между Ecore и EMOF существуют небольшие различия, главным образом, на уроне присваивания имен; однако EMF может выполнять явные операции чтения и записи сериализаций EMOF.

Определение модели EMF

Описание EMF начнем с рассмотрения тривиальной модели, состоящей из одного класса:

Модель, состоящая из одного класса

Эта модель изображает один класс с именем Book ("Книга") и двумя атрибутами: title ("название") типа String и pages ("страницы") типа int.

Определение нашей модели, такое же простое, как и она сама, может быть подготовлено для генератора кодов EMF несколькими способами.

UML

Если применяемое вами средство моделирования работает с EMF[2] , можно просто изобразить диаграмму классов, как показано выше.

XMI

Другой способ состоит в том, чтобы описать модель непосредственно в документе XMI, который может выглядеть так:

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

Документ XMI содержит ту же информацию, что и диаграмма класса, но в более компактном виде. Каждому классу и атрибуту в диаграмме соответствует определение класса или атрибута в документе XMI.

Код Java с аннотациями

Для тех, у кого нет ни инструмента графического моделирования, ни интереса к вводу всех синтаксических конструкций XMI вручную, предлагается третий вариант описания модели. Генератор EMF выполняет объединение кодов, поэтому если заранее подготовить неполные интерфейсы Java (с аннотациями, содержащими информацию о модели), то генератор использует эти интерфейсы в качестве своих метаданных и объединит созданный им код с остальной реализацией.

Мы могли бы определить класс Book нашей модели на языке Java следующим образом:

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

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

При таком подходе вся информация о модели задается в форме интерфейсов Java со стандартными методами get[3] , применяемыми для идентификации атрибутов и ссылок. Тег @model используется для идентификации интерфейсов (или разделов интерфейса), соответствующих элементам модели, для которых генератор должен создавать код.

В нашем простом примере вся информация о модели содержится в самом интерфейсе Java, поэтому никакой дополнительной информации задавать не требуется. Однако, в общем случае в теге @model можно указать дополнительные сведения об элементе модели. Например, если для страниц необходимо задать атрибут "только для чтения", (то есть запретить создание метода set), то в аннотацию нужно добавить следующее:

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

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

Схема XML

Иногда необходимо описать модель с помощью схемы, которая задает вид сериализаций экземпляров. Это может быть полезным при создании приложения, которое должно использовать XML для объединения с существующим приложением или для работы под управлением стандартного приложения. Ниже показана схема, эквивалентная нашей простой модели книги:

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

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

В оставшейся части обзора для моделирования будут использоваться диаграммы UML (по причине их ясности и краткости). Все принципы моделирования, иллюстрируемые с помощью этих диаграмм, могут описываться с применением аннотаций Java или непосредственно с помощью XMI, а для большинства из них могут существовать эквивалентные схемы XML. Независимо от того, в каком виде предоставляется информация, код, генерируемый EMF, во всех случаях будет одинаковым.

Создание реализации Java

Интерфейс Java и соответствующий класс реализации будут генерироваться для каждого класса модели. В нашем примере интерфейс, который генерируется для класса Book, может выглядеть следующим образом:

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

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

Каждый такой интерфейс содержит методы получения и установки всех атрибутов и ссылок соответствующего класса модели.

Интерфейс Book расширяет базовый интерфейс EObject. EObject - это существующий в EMF эквивалент класса java.lang.Object, то есть он представляет собой базовый класс для всех классов EMF. EObject и его соответствующий класс реализации EObjectImpl (который будет рассмотрен позднее) предоставляют относительно простой базовый класс, который позволяет включить класс Book в среду уведомлений и среду постоянного хранения EMF. Применение EObject в модели будет описано позже, сейчас же продолжим наше рассмотрение создания класса Book в среде EMF.

Каждый создаваемый класс реализации включает методы получения (get) и установки (set), определенные в соответствующем интерфейсе, плюс некоторые другие методы, требуемые средой EMF.

Класс BookImpl будет также содержать реализации методов доступа для атрибутов title и pages. Для атрибута pages, например, генерируется следующая реализация:

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

    ...
  }

Для метода get создается максимально эффективный код. Он просто возвращает переменную экземпляра, представляющую атрибут.

Метод set, хотя и несколько более сложный, также достаточно эффективен. Помимо задания переменной экземпляра pages, в методе set вызывается метод eNotify() для отправки уведомления об изменении объекта всем наблюдателям, подключенным к этому объекту. Для оптимизации кода вызывается метод eNotificationRequired(), позволяющий исключить создание объекта уведомления (ENotificationImpl) и вызов метода eNotify() при отсутствии наблюдателей (например, при работе приложения в пакетном режиме). Реализация eNotificationRequired() по умолчанию просто проверяет, существуют ли какие-либо наблюдатели (адаптеры), подключенные к объекту. Следовательно, если объекты EMF используются без наблюдателей, то обращение к eNotificationRequired() не означает ничего, кроме проверки пустого указателя, который подставляется компилятором JIT.

Шаблоны доступа, генерируемые для других типов атрибутов, например, для атрибута title типа String, по существу, незначительно отличаются от шаблонов для атрибута pages[4] .

Для ссылок (особенно двунаправленных) создаются несколько более сложные методы доступа. Рассмотрение этих методов позволяет оценить значение генератора EMF.

Однонаправленные ссылки

Расширим рассмотренную в нашем примере модель, добавив в нее класс Writer ("Писатель"), связанный с классом Book:

Однонаправленная ссылка

Связь между книгой и писателем в данном примере изображается единственной однонаправленной ссылкой. Для доступа к классу Writer из класса Book применяется имя ссылки (роли) author.

Запуск этой модели в генераторе EMF приведет к созданию нового интерфейса Writer и класса реализации WriterImpl, а также к созданию в интерфейсе Book дополнительных методов get и set:

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

Поскольку ссылка author однонаправленная, то реализация метода setAuthor() очень похожа на реализацию простого метода установки данных, например, на рассмотренную выше реализацию для setPages():

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

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

Однако, поскольку мы имеем дело со ссылкой на объект, то метод getAuthor() немного сложнее. Это связано с тем, что для метода get для некоторых типов ссылок (в том числе для типа author) необходимо учесть возможность существования объекта, на который указывает ссылка (в данном случае - Writer), в другом ресурсе (документе), отличном от исходного объекта (в данном случае - Book). Поскольку среда постоянного хранения EMF использует схему частичной загрузки, то указатель на объект (в данном случае - author) может в некоторый момент времени быть промежуточным для данного объекта, а не указателем на фактический объект, на который указывает ссылка[5] . В результате метод getAuthor() выглядит следующим образом:

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

Вместо того чтобы просто возвращать переменную экземпляра author, сначала вызывается наследуемый метод среды eIsProxy() для проверки, не является ли эта ссылка промежуточной (proxy), а в случае положительного ответа вызывается метод eResolveProxy(). Последний вызывает метод EcoreUtil.resolve() - статический служебный метод, который пытается загрузить документ целевого объекта, а затем - сам объект, используя URI (универсальный идентификатор ресурса) промежуточной ссылки. В случае успеха метод возвращает обработанный объект. Если, однако, документ загрузить не удается, он повторно возвращает только промежуточную ссылку[6] .

Двунаправленные ссылки

Итак, мы разобрались, как разрешение промежуточных ссылок влияет на шаблон get для определенных типов ссылок; теперь можно посмотреть, как изменится шаблон set, если связь будет двусторонней. Изменим нашу одностороннюю связь author на двустороннюю:

Двунаправленная ссылка

Теперь связь - двусторонняя, на что указывает отсутствие стрелки на конце линии связи со стороны автора (Writer). Для доступа к классу Books из класса Writer используется роль с именем books.

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

  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, ...)); // отправка уведомления об "изменении"
  }

Как вы можете видеть, при задании двунаправленной ссылки, такой как author, необходимо также задать ее второй конец (с помощью вызова eInverseAdd()). Необходимо также удалить инверсию всех предыдущих ссылок author (с помощью вызова eInverseRemove()), так как в нашей модели ссылка author - единственная (то есть книга может иметь только одного автора)[7] , и, следовательно, данная книга может быть указана в ссылке books не более чем одного автора (Writer). Наконец, для установки ссылки author вызывается другой генерированный метод (basicSetAuthor()), который выглядит следующим образом:

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

Этот метод очень похож на метод set для однонаправленной ссылки, за исключением того, что уведомление не запускается напрямую, а добавляется к ненулевому аргументу msgs[8] . Поскольку во время операции установки двунаправленной ссылки возможно прямое/обратное добавление/удаление, то могут генерироваться четыре (в данном примере - три) различных уведомления. Для сбора всех этих уведомлений применяется объект NotificationChain; таким образом, можно отложить запуск уведомлений до того момента, пока не будут выполнены все эти изменения состояния. Для отправки уведомлений, стоящих в очереди, вызывается метод msgs.dispatch(), как показано выше в методе setAuthor().

Множественные ссылки

Как вы могли заметить, связь books в нашем примере (от Writer к Book) - множественная (то есть 0..*). Другими словами, перу одного писателя могут принадлежать несколько книг. Для работы с множественными ссылками (то есть ссылками, для которых верхняя граница больше 1) в EMF применяется API набора, поэтому в этом интерфейсе создается только метод get:

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

Заметим, что getBooks() возвращает EList, а не java.util.List. В действительности это почти одно и то же. EList - это производный EMF-класс базового класса java.util.List, который добавляет в данный API два метода move. Иначе, с точки зрения клиента, его можно рассматривать как стандартный список Java. Например, для того чтобы добавить книгу в связь books, можно просто вызвать

  aWriter.getBooks().add(aBook);

а для того чтобы перебрать все книги, можно использовать следующий цикл:

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

Как видно из этого примера, с точки зрения клиента, API для работы с множественными ссылками не представляет из себя ничего особенного. Однако, поскольку ссылка books является частью двусторонней связи (это инверсия Book.author), нам по-прежнему необходимо выполнить установку обратных ссылок, как было показано для метода setAuthor(). Для того чтобы понять, как обрабатывается множественная ссылка, взгляните на реализацию метода getBooks() в классе WriterImpl:

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

Метод getBooks() возвращает специальный класс реализации EObjectWithInverseResolvingEList, который создается со всей информацией, необходимой для установки обратной связи при вызове функций add и remove. Фактически EMF предоставляет 20 различных специальных реализаций EList[9]  для эффективной реализации всех типов множественных связей. Для односторонних связей (то есть связей без инверсии) используется класс EObjectResolvingEList. Если для ссылки не требуется выполнять промежуточное преобразование, то используется класс EObjectWithInverseEList или EObjectEList, и т.д.

Поэтому в нашем примере список, применяемый для реализации ссылки books, создается с аргументом LibraryPackage.BOOK__AUTHOR (статическая целая константа, представляющая обратную ссылку). Он будет использоваться при вызове add() для вызова eInverseAdd() для Book, подобно тому, как eInverseAdd() вызывался для Writer при вызове setAuthor(). Вот так метод eInverseAdd() выглядит в классе 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:
          ...
      }
    }
    ...
  }

Сначала вызывается метод eInverseRemove() для удаления всех предыдущих ссылок author (как описывалось ранее при рассмотрении метода setAuthor()), затем вызывается метод basicSetAuthor() для фактической установки ссылки. В нашем частном примере существует только одна двунаправленная ссылка, однако в методе eInverseAdd() используется оператор выбора switch, который содержит вариант для проверки всех двунаправленных ссылок, доступных в классе Book[10] .

Ссылки включения

Добавим новый класс Library, который будет служить контейнером для Books.

Ссылка включения

Ссылка включения обозначается черным ромбиком на конце связи со стороны библиотеки (Library). Полностью эта связь указывает, что библиотека (Library) объединяет по значению произвольное число (0 и более) книг (Books). Важность связей включения (объединения по значению) определяется тем, что они идентифицируют предка или владельца целевого экземпляра, то есть фактическое расположение постоянного объекта.

Применение включений отражается в генерируемом коде следующим образом. Прежде всего, поскольку объект, включенный в контейнер, гарантированно находится в том же ресурсе, что и контейнер, то промежуточное преобразование (ссылки) не требуется. Следовательно, создаваемый метод get класса LibraryImpl будет использовать класс реализации EList без преобразования ссылок:

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

Кроме того, EObjectContainmentEList очень эффективно реализует операцию contains() (затрачивая постоянное, а не линейно возрастающее время, как в обычном случае). Это особенно важно потому, что в списках ссылок EMF не допускаются одинаковые записи, и contains() вызывается также при выполнении операций add().

Поскольку объект может содержаться только в одном контейнере, то добавление объекта в связь включения означает его удаление из любого контейнера, в котором он в данный момент находится, независимо от реально существующей связи. Например, добавление книги (Book) в список книг библиотеки (Library) может вызвать удаление этой книги из другого списка книг библиотеки. Эта связь ничем не отличается от любой другой двусторонней связи с инверсией, множественность которой равна 1. Предположим, однако, что для класса Writer существует также связь включения с классом Book, называемая ownedBooks. В том случае, если экземпляр данной книги находится в списке ownedBooks некоторого автора (Writer), то при добавлении этого экземпляра в ссылку books библиотеки (Library) следует сначала удалить его из класса Writer.

Для эффективной реализации этого алгоритма в базовом классе EObjectImpl вводится переменная экземпляра (eContainer) типа EObject, которая используется для хранения контейнера в общем виде. В результате ссылки включения всегда неявно будут двусторонними. Для доступа к Library из класса Book можно использовать код следующего вида:

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

Если вы хотите избежать приведения типов, можно задать явное описание двусторонней связи:

Двунаправленная ссылка включения

и позволить EMF создать метод get с контролем типов:

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

Обратите внимание, что явный метод get использует переменную eContainer класса EObjectImpl вместо переменной экземпляра, созданной генератором, как мы видели ранее для ссылок в модели без контейнера (см. обсуждение метода getAuthor() выше)[11] .

Перечислимые атрибуты

До сих пор мы обсуждали, каким образом EMF обрабатывает простые атрибуты и различные типы ссылок. Еще один общеупотребительный тип атрибута - перечислимый. Для реализации атрибутов перечислимого типа применяется шаблон Java с контролем типов enum[12] .

Если добавить в класс Book перечислимый атрибут category:

Перечислимый атрибут и его определение

и выполнить повторную генерацию классов реализации, то интерфейс Book будет содержать методы получения и установки атрибута category:

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

В созданном интерфейсе методы category используют класс перечисления с контролем типов с именем BookCategory. Этот класс определяет статические константы для значений перечислителя и другие удобные методы, например:

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

Как можно видеть, класс перечисления предоставляет как статические целые константы для значений перечислителя, так и статические константы для самих перечисляемых элементов. Имена целых констант совпадают с именами литеральных констант модели[13] . Литеральные константы имеют те же самые имена, только с добавлением _LITERAL.

Эти константы обеспечивают удобный доступ к литералам, например, при установке категории книги:

  book.setCategory(BookCategory.SCIENCE_FICTION_LITERAL);

Конструктор класса BookCategory - закрытый, поэтому единственные экземпляры класса перечисления экземпляры, которые могут существовать - это экземпляры этого класса для статических констант MYSTERY_LITERAL, SCIENCE_FICTION_LITERAL и BIOGRAPHY_LITERAL. В результате проверка равенства (то есть вызов .equals()) никогда не требуется. Для надежного сравнения литералов всегда можно использовать простой и более эффективный оператор == :

  book.getCategory() == BookCategory.MYSTERY_LITERAL

Для сравнения с несколькими значениями еще лучше использовать оператор выбора (switch) с целыми метками:

  switch (book.getCategory().value()) {
    case BookCategory.MYSTERY:
      // do something ...
      break;
    case BookCategory.SCIENCE_FICTION:
      ...
  }

В тех случаях, когда доступно только имя литерала (типа String) или значение (типа int), в классе перечисления создаются удобные методы get(), позволяющие получить соответствующий объект литерала.

Фабрики и группы

Помимо интерфейсов и классов реализации, EMF создает для модели еще два дополнительных интерфейса (и класса реализации): фабрику (factory) и группу (package).

Как следует из названия, фабрика используется для создания экземпляров классов модели, тогда как группа предоставляет некоторые статические константы (например, константы, используемые создаваемыми методами) и подходящие методы для доступа к метаданным модели[14] .

Ниже приведен интерфейс фабрики для примера с книгой:

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

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

    LibraryPackage getLibraryPackage();
  }

Как видно из этого примера, генерируемая фабрика предоставляет методы создания (create) для всех определенных в модели классов, метод доступа для группы (package) данной модели и ссылку типа static constant (то есть eINSTANCE) на экземпляр фабрики.

Интерфейс LibraryPackage обеспечивает удобный доступ ко всем метаданным нашей модели:

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

    ...
  }

Как видно из этого примера, метаданные доступны в двух видах: как целые константы и как сами мета-объекты Ecore. Целые константы предоставляют наиболее эффективный способ передачи метаинформации. Как вы могли заметить, эти константы используются созданными методами в своих реализациях. Позднее, когда мы будем рассматривать способы реализации адаптеров EMF, вы увидите, что эти константы также эффективно используются при обработке уведомлений об изменениях. Кроме того, как и в случае интерфейса factory, созданный интерфейс package предоставляет ссылку типа static constant на реализацию его экземпляра.

Создание классов из базовых классов

Предположим, что мы хотим создать производный класс SchoolBook класса Book нашей модели:

Единичное наследование

Генератор EMF обрабатывает единичное наследование так, как и ожидается: созданный им интерфейс расширяет базовый интерфейс:

  public interface SchoolBook extends Book

а класс реализации расширяет базовый класс реализации:

  public class SchoolBookImpl extends BookImpl implements SchoolBook

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

Рассмотрим следующий пример:

Множественное наследование

Здесь класс SchoolBook порождается двумя классами: Book и Asset. Класс Book описывается как базовый (расширяемый) класс реализации[15] . Теперь при повторном создании модели интерфейс SchoolBook будет расширять оба эти интерфейса:

  public interface SchoolBook extends Book, Asset

Класс реализации выглядит так же, как и раньше, только теперь он включает реализации смешанных методов getValue() и setValue():

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

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

Настройка созданных классов реализации

Для созданных классов Java можно добавлять методы и переменные экземпляров, не заботясь о том, что сделанные изменения будут потеряны, если впоследствии вы решите изменить модель и повторно создать для нее новый код. Например, добавим в класс Book метод isRecommended(). Для этого достаточно просто добавить в начало интерфейса Book сигнатуру нового метода:

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

а в класс BookImpl - его реализацию:

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

Генератор EMF не уничтожит это изменение, потому что это, во-первых, не генерируемый метод. Каждый метод, генерируемый EMF, включает комментарий Javadoc, который содержит тег @generated:

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

Любой метод в файле, который не содержит этот тег (как, например, метод isRecommended()) останется неизменным, когда бы ни выполнялась повторная генерация кода. Фактически, для того чтобы изменить реализацию генерированного метода, достаточно удалить из него тег @generated [16] :

  /**
   * ...
   * @generated
   */
  public String getTitle()
  {
    // наша настраиваемая реализация ...
  }

Теперь, поскольку тег @generated пропускается, метод getTitle() рассматривается как пользовательский код; если выполнить для модели повторную генерацию кода, генератор обнаружит конфликтную ситуацию и просто аннулирует генерированную версию метода.

В действительности, перед тем как аннулировать генерированный метод, генератор сначала проверит, нет ли в данном файле другого генерированного метода с тем же именем, к которому добавлен суффикс Gen. Если такой метод найдется, то генератор, вместо того чтобы аннулировать вновь созданную версию этого метода, перенаправит в нее вывод. Например, если требуется расширить генерированную реализацию метода getTitle(), а не аннулировать ее полностью, то ее можно просто переименовать следующим образом:

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

а затем добавить наше переопределение как пользовательский метод, который выполняет любые заданные операции:

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

Если выполнить повторную генерацию кода, то генератор обнаружит конфликт с пользовательской версией getTitle(); однако, поскольку в классе также есть метод @generated getTitleGen(), генератор не аннулирует его, а перенаправит в него вновь созданную реализацию.

Операции в моделях EMF

Кроме атрибутов и ссылок, в классы модели можно добавлять операции. При этом генератор EMF будет создавать их сигнатуру в интерфейсе, а структуру метода - в классе реализации. EMF не моделирует поведение модели, поэтому для ее реализации необходимо ввести код Java, созданный пользователем.

Для этого можно удалить тег @generated из созданной реализации (как описано выше) и добавить прямо в нее нужный код. Можно также включить код Java прямо в модель. Если вы работаете с инструментом моделирования Rose, то код можно ввести в текстовое поле на вкладке Semantics в окне Operation Specification. Этот код будет сохранен в модели EMF в виде аннотации к операции[17] , а затем будет генерироваться в ее теле.

Применение созданных EMF классов

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

Используя созданные генератором классы, программа клиента может создать и инициализировать класс Book с помощью следующих простых операторов Java:

  LibraryFactory factory = LibraryFactory.eINSTANCE;

  Book book = factory.createBook();

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

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

Поскольку связь author между Book и Writer двусторонняя, то автоматически инициализируется обратная ссылка (books). Это можно проверить, организовав следующий цикл для ссылки books:

  System.out.println("Книги Шекспира:");
  for (Iterator iter = writer.getBooks().iterator(); iter.hasNext(); )
  {
    Book shakespeareBook = (Book)iter.next();
    System.out.println("  заглавие: " + shakespeareBook.getTitle());
  }

В результате выполнения этой программы вывод будет выглядеть примерно следующим образом:

  Книги Шекспира:
    заглавие: Король Лир

Загрузка и сохранение ресурсов

Для создания документа с именем mylibrary.xmi, содержащего вышеописанную модель, достаточно в начале программы создать ресурс EMF, поместить в него объекты book и writer и в конце программы вызвать метод save():

  // Создать набор ресурсов.
  ResourceSet resourceSet = new ResourceSetImpl();

  // Получить URI файла модели.
  URI fileURI = URI.createFileURI(new File("mylibrary.xmi").getAbsolutePath());

  // Создать для этого файла ресурс.
  Resource resource = resourceSet.createResource(fileURI);

  // Добавить к его содержимому объекты book и writer.
  resource.getContents().add(book);
  resource.getContents().add(writer);

  // Сохранить содержимое ресурса в файловой системе.
  try
  {
    resource.save(Collections.EMPTY_MAP);
  }
  catch (IOException e) {}

Обратите внимание, что для создания ресурса EMF используется набор ресурсов (интерфейс ResourceSet). Набор ресурсов используется средой EMF для управления ресурсами, которые могут содержать перекрестные ссылки на документы. Для того чтобы создать для данного URI ресурс правильного типа на основе его схемы, расширения файла или других возможных критериев, EMF использует реестр (интерфейс Resource.Factory.Registry)[18] . Во время загрузки он также управляет загрузкой по запросу перекрестных ссылок на документы.

В результате выполнения этой программы будет создан файл mylibrary.xmi с примерно следующим содержимым:

  <xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"
      xmlns:library="http:///library.ecore">
    <library:Book title="Король Лир" author="/1"/>
    <library:Writer name="Уильям Шекспир" books="/0"/>
  </xmi:XMI>

Если бы мы решили поместить книги и авторов в разные документы, то все, что требовалось бы сделать - это открыть второй документ:

  Resource anotherResource = resourceSet.createResource(anotherFileURI);

и добавить объект writer в этот документ, а не в первый ресурс:

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

В этом случае будут созданы два ресурса, содержащих по одному объекту каждый, с перекрестной ссылкой друг на друга.

Обратите внимание, что ссылка включения обязательно подразумевает, что контейнер и входящий в него объект находятся в одном и том же ресурсе. Предположим, например, что мы создали экземпляр библиотеки Library; наша книга (Book) включается в него с помощью ссылки включения books. При этом Book автоматически удаляется из содержимого данного ресурса, который в этом смысле также работает как ссылка включения. Если затем добавить Library в этот ресурс, то ресурсу будет неявно принадлежать и книга, и ее параметры можно снова сериализовать в нем.

ВЫ можете сериализовать свои объекты в формате, отличном от XMI. Для этого вы должны предоставить свой собственный код сериализации и синтаксического анализа. Создайте свой собственный класс ресурса (как производный класс класса ResourceImpl), реализующий выбранный формат сериализации, а затем зарегистрируйте его либо в локальном наборе ресурсов, либо в глобальном реестре фабрики (если он всегда должен использоваться с вашей моделью).

Наблюдение (адаптация) объектов EMF

Ранее, при рассмотрении методов set в генерированных классах EMF мы видели, что при изменении атрибута или ссылки всегда отправляется уведомление. Например, метод BookImpl.setPages() содержал следующую строку:

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

Каждый EObject может содержать список наблюдателей (адаптеров), которым отправляются уведомления при любых изменениях состояния. Метод eNotify() среды перебирает список и отправляет указанным в нем наблюдателям уведомление.

Для подключения наблюдателя к любому EObject (например, к book) его можно добавить в список eAdapters:

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

Однако, как правило, адаптеры добавляются в EObjects с помощью фабрики адаптеров. Помимо использования в качестве наблюдателей, адаптеры могут применяться для расширения способов обработки объекта, к которому они подключены. Для подключения такого дополнительного способа обработки клиент должен отправить запрос к фабрике адаптеров на адаптацию объекта с расширением нужного типа. Обычно это выглядит следующим образом:

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

Обычно requiredType представляет собой некоторый интерфейс, поддерживаемый данным адаптером. Например, в качестве фактического аргумента может быть указан java.lang.Class для интерфейса выбранного адаптера. Возвращаемый адаптер может быть затем явно приведен к запрошенному интерфейсу:

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

Адаптеры часто используются таким образом для того, чтобы расширить способы обработки объекта, не создавая для этого производных классов.

Для обработки уведомлений в адаптере необходимо переопределить метод eNotifyChanged(), вызываемый для каждого зарегистрированного адаптера в методе eNotify(). Типичный адаптер реализует метод eNotifyChanged() для выполнения некоторого действия для нескольких или для всех уведомлений (в зависимости от типа уведомления).

Иногда адаптеры создаются для адаптации определенного класса (например, Book). В этом случае метод notifyChanged() может выглядеть примерно так:

  public void notifyChanged(Notification notification)
  {
    Book book = (Book)notification.getNotifier();
    switch (notification.getFeatureID(Book.class))
    {
      case LibraryPackage.BOOK__TITLE:
        // book title changed
        doSomething();
        break;
      caseLibraryPackage.BOOK__CATEGORY:
        // book category changed
        ...
      case ...
    }
  }

Аргумент Book.class, передаваемый при вызове notification.getFeatureID(), позволяет учесть, что адаптируемый объект может быть не экземпляром класса BookImpl, а экземпляром производного класса с множественным наследованием,в котором класс Book не является основным (первым) интерфейсом. В этом случае ИД элемента, передаваемый в notification, будет числом относительно другого класса и следовательно, его необходимо откорректировать перед вызовом оператора выбора (switch) с метками BOOK__. В случае единичного наследования этот аргумент игнорируется.

Существует другой общий тип адаптера, который не связан ни с одним классом, а использует для выполнения своей функции рефлективный API EMF. Вместо вызова getFeatureID() в уведомлении он может вызывать метод getFeature(), который возвращает фактический элемент Ecore (то есть объект в метамодели, представляющий этот элемент).

Применение рефлективного API

Для работы с генерированными классами модели можно применять рефлективный API, определенный в интерфейсе EObject:

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

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

С помощью рефлективного API мы может установить имя автора:

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

или получить это имя следующим образом:

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

Обратите внимание, что элемент, к которому обращается метод, идентифицируется метаданными, полученными из единственного экземпляра группы библиотек (LibraryPackage).

Применение рефлективного API несколько менее эффективно, чем непосредственный вызов генерированных методов getName() и setName()[19] , однако, открывает возможность общего доступа к модели. Например, рефлективные методы используются средой EMF.Edit для реализации полного набора групповых команд (например, AddCommand, RemoveCommand, SetCommand), который можно применять для любой модели. За дополнительной информацией обратитесь к разделу Среда EMF.Edit - Обзор.

Помимо eGet() и eSet() рефлективный API включает два других связанных метода: eIsSet() и eUnset(). Метод eIsSet() позволяет проверить, установлен ли атрибут[20] , а метод eUnset() может использоваться для сброса атрибута или его установки в исходное состояние. Метод eIsSet(), например, используется стандартным сериализатором XMI для определения, какие атрибуты следует сериализовать при выполнении операции сохранения ресурса.

Дополнительные вопросы

Флаги, управляющие генерацией кода

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

Типы данных

Как упоминалось выше, все классы, определенные в модели (например, Book, Writer) неявно порождаются из базового класса EMF EObject. Однако, не обязательно все используемые в модели классы должны быть классами EObjects. Предположим, например, что в модель требуется добавить атрибут типа java.util.Date. Для того чтобы это сделать, необходимо сначала определить тип данных EMF для представления внешнего типа. В UML для этой цели применяется тип данных stereotype:

Определение типа данных

Нетрудно заметить, что тип данных - это именованный элемент модели, который действует как "заместитель" для некоторого класса Java. Фактический класс Java предоставляется как атрибут типа javaclass stereotype, имя которого - представляемый полностью определенный класс. Определив этот тип данных, мы теперь можем объявлять атрибуты типа java.util.Date следующим образом:

Атрибут с типом данных как типом атрибута

При повторной генерации кода в интерфейсе Book появится атрибут publicationDate:

  import java.util.Date;

  public interface Book extends EObject
  {
    ...

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

Как можно видеть, этот атрибут типа Date обрабатывается почти так же, как любой другой. Тип данных всех атрибутов (включая атрибуты типа String, int и т.д.) фактически совпадает с их типом. Единственная особенность стандартных типов Java заключается в том, что их соответствующие типы данных предопределяются в модели Ecore, поэтому их не нужно переопределять в каждой модели, в которой они используются.

Использование определения типа данных приводит к еще одному результату для создаваемой модели. Поскольку типы данных представляют некоторый произвольный класс, у стандартной программы сериализации и синтаксического анализа (например, сериализатора XMI, применяемого по умолчанию) нет способа узнать, как сохранять состояние атрибута данного типа. Должен ли вызываться метод toString()? Среда EMF не требует обязательного вызова этого метода, и генерирует в классе реализации фабрики два дополнительных метода для каждого типа данных, определенных в модели:

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

По умолчанию, эти методы просто вызывают реализации базового класса, которые предоставляют подходящие, но неэффективные методы по умолчанию: convertToString() просто вызывает toString() для instanceValue, однако createFromString() пытается вызвать конструктор строк Java или, в случае неудачи, статический метод valueOf() (если он существует). Рекомендуется удалить для этих методов теги @generated и изменить их, чтобы получить пользовательские реализации:

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

Модель Ecore

Ниже показана полная иерархия классов модели Ecore (серые квадратики - это абстрактные классы):

Иерархия классов Ecore

Как можно видеть, вводится небольшое число классов, представляющих объекты EMF, описанные в этом документе: классы (и их атрибуты, ссылки и операции), типы данных, перечислители, группы и фабрики.

Реализация EMF модели Ecore сама создается с помощью генератора EMF и точно так же проста и эффективна, как реализация, описанная в предыдущих разделах этого документа.



[1] В действительности метамодель EMF сама является моделью EMF, сериализованная форма по умолчанию которой - это XMI.

[2] В настоящее время EMF поддерживает импорт из Rational Rose, однако архитектура генератора позволяет работать с моделями, созданными с помощью других инструментов моделирования.

[3] EMF использует подмножество шаблонов имен методов доступа к простым свойствам JavaBean.

[4] Есть несколько опций, с помощью которых пользователи могут изменять создаваемые шаблоны. Их описание приведено далее в этом документе (в разделе Флаги, управляющие генерацией кода).

[5] Ссылки включения, которые будут рассмотрены далее (см. раздел Ссылки включения), не могут указывать на несколько документов. Пользователи могут установить в метаданных ссылки специальный флаг, указывающий, что вызывать метод разрешения ссылки не требуется, поскольку эта ссылка никогда не будет использоваться в файле с перекрестными ссылками на документы (см. раздел Флаги, управляющие генерацией кода). В этих случаях генерированный метод get просто возвращает указатель.

[6] Приложения, которым приходится иметь дело и обрабатывать неработающие ссылки, должны вызывать eIsProxy() для объекта, возвращаемого методом get, чтобы проверить, разрешается эта ссылка или нет (например, book.getAuthor().eIsProxy()).

[7] Для упрощения модели возможность существования нескольких авторов не рассматривается.

[8] Метод basicSet() также вызывается в методах eInverseAdd() и eInverseRemove(), которые будут рассмотрены несколько позже.

[9] В действительности все конкретные реализации EList являются простыми производными классами одного очень функционального и эффективного базового класса реализации - EcoreEList.

[10] В eInverseAdd() вместо простого выбора варианта, соответствующего заданному ИД элемента, сначала вызывается метод eDerivedStructuralFeatureID(featureID, baseClass). Для простых моделей с единичным наследованием этот метод имеет реализацию по умолчанию, которая игнорирует второй аргумент и возвращает переданный featureID. Для моделей с множественным наследованием eDerivedStructuralFeatureID() может подменяться методом генерированного производного класса, который устанавливает вместо ИД элемента относительно частично определенного класса (то есть baseClass) ИД элемента относительно конкретного производного класса данного экземпляра.

[11] EObjectImpl также содержит переменную экземпляра eContainerFeatureID целого типа для отслеживания ссылки, используемой в данный момент для eContainer.

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

[13] Если моделируемые имена литеральных констант содержат символы нижнего регистра, то в соответствии со стилем программирования на Java имена статических констант преобразуются в верхний регистр.

[14]  Использование интерфейсов Factory или Package в программе не является обязательным требованием. Однако, для того чтобы стимулировать применение фабрики для создания экземпляров классов, EMF создает в классах модели защищенные конструкторы, то есть не позволяет просто вызывать new для создания экземпляров. Однако доступ к открытой части созданных классов можно изменить вручную, если это действительно нужно. При возможно последующем повторном создании классов внесенные вручную изменения переопределены не будут.

[15] Фактически, первый базовый класс в модели Ecore - это класс, используемый как базовый класс реализации. Для того чтобы указать, что класс Book должен быть первым в представлении Ecore, в диаграмме UML следует ввести <<extend>> stereotype.

[16] Если вы заранее знаете, что для некоторого элемента будет применяться ваша собственная реализация, то наилучший способ предоставить эту реализацию состоит в следующем: смоделируйте этот атрибут как подвижный (volatile), чтобы генератор создал только шаблон тела метода, а затем используйте его для создания своей реализации.

[17] В EMF предусмотрен общий механизм задания для объектов метамодели аннотаций с дополнительной информацией. С его помощью также можно подключать к элементам модели пользовательскую документацию, а если модель создается на основе схемы XML, EMF использует этот механизм для получения параметров сериализации, которые невозможно описать с помощью Ecore.

[18] Приведенный выше код невозможно выполнить в автономном режиме (то есть вызвав его прямо на виртуальной машине Java с указанием нужных файлов JAR EMF в параметре classpath), так как реестр фабрики ресурсов не инициализирован значением фабрики ресурсов по умолчанию. Сразу после строки, создающей набор ресурсов, следует вставить следующую строку:

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

Тогда набор ресурсов будет использовать реализацию ресурса XMI по умолчанию для сериализации. Такая же регистрация выполняется автоматически в глобальном реестре фабрики ресурсов, когда EMF запускается в Eclipse.

[19] Для каждого класса модели также создаются реализации рефлективных методов. Они включают тип элемента и просто вызывают соответствующие генерированные методы контроля типов.

[20] Что подразумевается по установленным атрибутом, объясняется в описании флага Unsettable в разделе Флаги, управляющие генерацией кода.

[21] Хорошенько подумайте, прежде чем объявлять для элемента отмену преобразования промежуточных ссылок. Пусть вы не хотите использовать перекрестные ссылки, но это еще не означает, что их не захочет использовать тот, кто будет работать с вашей моделью. Установить флаг, запрещающий для элемента преобразование промежуточных ссылок, - это примерно то же самое, что объявить класс Java конечным.