Среда EMF.Edit - Обзор

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

Этот документ предполагает, что вы знакомы с основными понятиями EMF (Eclipse Modeling Framework). За дополнительной информацией о EMF обратитесь к документу Eclipse Modeling Framework (EMF) - Обзор.

Введение

Если у вас есть модель на основе EMF, код для которой создан с помощью генератора EMF, то для добавления в нее пользовательских интерфейсов можно воспользоваться средой EMF.Edit.

EMF.Edit - это среда Eclipse, включающая повторно используемые параметризованные классы, позволяющие создавать редакторы для моделей EMF. Она предоставляет:

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

Отображение объектов EMF в средствах отображения JFace

Что такое поставщик содержимого?

Среда пользовательского интерфейса Eclipse (JFace) включает набор повторно используемых классов средств отображения (например, TreeViewer, TableViewer), предназначенных для отображения структурированных моделей. Для средств отображения JFace не требуется, чтобы объекты модели выводились в соответствии с каким-то определенным протоколом (то есть реализовывали какой-то специальный интерфейс), поскольку они работают с объектами любого типа (то есть с любыми производными классами java.lang.Object). Это возможно потому, что средства отображения не управляют объектами модели напрямую, а обращаются к ним через объект адаптера, называемый поставщиком содержимого (content provider).

Каждый класс средства отображения использует поставщик содержимого, который реализует конкретный интерфейс. Например, класс TreeViewer использует поставщик содержимого, реализующий следующий интерфейс:

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

Основная структура показана на следующей диаграмме:

Поставщик содержимого JFace

Класс TreeViewer отображает на экране дерево объектов (называемых элементами), причем каждый объект (за исключением объекта входа, или корневого объекта) предоставляется ему путем вызова метода getChildren() в его поставщике содержимого.

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

предоставление содержимого для модели EMF

Для предоставления содержимого для моделей EMF среда EMF.Edit предоставляет параметризованный класс реализации поставщика содержимого. Класс AdapterFactoryContentProvider реализует интерфейсы поставщиков содержимого, передавая управление адаптерам EMF, которые "знают", какой способ навигации по объектам (элементам) модели применяется средствами отображения. Например, класс адаптера EMF, применяемый для поддержки средства отображения дерева, мог бы реализовать следующий интерфейс EMF.Edit:

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

Обратите внимание на сходство этого интерфейса с описанным выше интерфейсом ITreeContentProvider поставщика содержимого. Класс AdapterFactoryContentProvider реализует интерфейс поставщика содержимого путем нахождения адаптера (реализующего интерфейс поставщика элементов (item provider)) для нужного элемента и передачи управления этому адаптеру. Переход от термина "объект" (object) к термину "элемент" (item) произведен намеренно: с точки зрения окна просмотра это не объекты, а элементы.

Диаграмма EMF выглядит следующим образом:

AdapterFactoryContentProvider передает управление поставщикам элементов

Примечание: Встроенный генератор среды EMF.Edit позволяет автоматически создавать для данной модели EMF классы ItemProviderAdapterFactory и ItemProvider. Дополнительная информация об этом приводится ниже.

AdapterFactoryContentProvider создается фабрикой адаптеров, которая, как и любая другая фабрика адаптеров EMF, служит для создания и определения расположения адаптеров определенного типа (в данном случае - поставщиков элементов (ItemProvider)). Поставщик содержимого обслуживает запрос, например, getChildren(), вызывая метод adapt(item) в ItemProviderAdapterFactory, который создает или возвращает ItemProvider (адаптер) для указанного элемента. Затем он просто вызывает метод getChildren() требуемого интерфейса (в данном случае - интерфейса ITreeItemProvider).

Метод getChildren() в AdapterFactoryContentProvider выглядит примерно так:

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

Этот же шаблон используется для всех методов поставщика содержимого. Как было установлено ранее, AdapterFactoryContentProvider просто передает методы поставщика содержимого конкретному поставщику элементов (адаптеру), который "знает", как обслуживать данный запрос.

В рассмотренном выше методе getChildren() объект, передаваемый в adapterFactory.adapt(), - это простой объект java.lang.Object (а не org.eclipse.emf.ecore.EObject). Это важная особенность среды EMF.Edit. Данная среда была специально разработана для работы с представлениями моделей EMF, которые могут отличаться от самой модели (то есть представления могут не включать некоторые фактические объекты, но включать другие (фиктивные) объекты). Для того чтобы разрешить смешение объектов EMF с другими (не-EMF) объектами, базовый класс среды для фабрик адаптеров предоставляет реализацию метода adapt(), которая работает следующим образом:

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

Если указанный объект не является классом Notifier среды EMF[1] , то возвращается сам объект. Такой подход позволяет поставщику элементов помещать в представление нужные элементы не-EMF типа, просто возвращая их (например, с помощью своего метода getChildren()). Поскольку возвращаемый объект реализует требуемый интерфейс поставщика элементов средства просмотра (например, интерфейс ITreeItemProvider), он будет обрабатываться так же, как и все остальные элементы EMF.

Это объясняет, почему мы предпочитаем вместо адаптеров вызывать поставщики элементов классов поставщиков/адаптеров. В нетривиальных приложениях отображаемая модель (то есть модель, предоставляемая поставщиком содержимого средства просмотра) одновременно может содержать как "реальные" объекты модели (EMF), поставщики элементов которых могут быть адаптерами (EMF), так и "фиктивные" объекты, поставщики элементов которых представляют собой сами эти объекты. Таким образом, все адаптеры являются поставщиками элементов, обратное же утверждение не верно.

Поставщики меток JFace

В предыдущих разделах обсуждалось, как средства отображения JFace используют поставщик содержимого для получения элементов своего содержимого. Подобный подход применяется и для получения изображения метки и текста для элементов, отображаемых средством отображения. Вместо того чтобы запрашивать для меток сами элементы, средство отображения использует другой объект, называемый поставщик меток (label provider) (подобный поставщику содержимого). Например, для получения меток элементов дерева класс TreeViewer передает методы объекту, реализующему интерфейс ILabelProvider.

Предоставление меток для объектов EMF

Для реализации поставщиков меток для моделей EMF в среде EMF.Edit применяется такой же механизм, как и для предоставления содержимого. Параметризованный класс реализации поставщика меток AdapterFactoryLabelProvider (работающий так же, как и класс AdapterFactoryContentProvider) передает (делегирует) интерфейс ILabelProvider в поставщики элементов для модели (те же поставщики меток, которые предоставляют содержимое). Расширенная диаграмма выглядит следующим образом:

AdapterFactoryLabelProvider передает управление поставщикам элементов

Поставщик содержимого и поставщик меток может (и обычно будет) передавать управление в ту же фабрику адаптеров и, следовательно, в те же поставщики элементов. Поставщики элементов, как и поставщики содержимого, расположены там, где в действительности выполняется работа.

Используя классы поставщиков EMF.Edit, пользователь может сконструировать класс TreeViewer для какой-нибудь модели следующим образом:

  myAdapterFactory = ...

  treeViewer = new TreeViewer();

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

Этот класс TreeViewer затем можно вывести, например, в каком-нибудь окне редактора обычным способом (предписанным для JFace).

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

Классы реализации поставщика элементов

Как было показано в предыдущем разделе, реальная работа по предоставлению данных для моделей EMF выполняется подключенными к модели адаптерами поставщиков элементов. На предыдущей диаграмме намеренно не были указаны количество и типы адаптеров ItemProvider. Это связано с тем, что среда EMF.Edit поддерживает для адаптеров поставщиков элементов два разных шаблона :

  1. Шаблон адаптеров с изменяемым состоянием: один объект адаптера для каждого объекта в модели
  2. Шаблон единого адаптера: один объект адаптера для каждого типа объекта в модели (рекомендуется использовать там, где это возможно)

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

Адаптеры поставщиков элементов с изменяемым состоянием

При применении первого шаблона между каждым объектом модели и его адаптером существует однозначное соответствие. Каждый адаптер имеет указатель (называемый target) на один и только один объект, который он адаптирует.

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

Адаптеры поставщиков элементов с изменяемым состоянием

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

Шаблон единого адаптера поставщиков элементов

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

Шаблон единого адаптера поставщиков элементов

На этом рисунке для объектов, как обычно, существуют указатели на адаптеры, но для поставщиков элементов (которые являются общими) нет обратных указателей на объекты. Если вспомнить интерфейс поставщика элементов дерева, который рассматривался ранее в разделе, где описывался поставщик содержимого, то можно заметить, что все его методы содержали дополнительный аргумент - объект (object):

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

Аргумент object добавлялся к каждому интерфейсу поставщика элементов специально для поддержки данного шаблона. В случае шаблона адаптера с изменяемым состоянием этот объект всегда будет совпадать с указателем target адаптера.

Может возникнуть вопрос: почему бы не использовать один "единственный шаблон адаптера" для всех объектов? Ответ на него прост: хотя это второй возможный шаблон (совместимый со средой EMF.Edit)[2] , использовать его не рекомендуется, так как полностью динамическая реализация, несмотря на простоту, трудна для настройки (если не использовать для этого большое число вызовов instanceof(), запутывающих код). Альтернатива состоит в использовании типизированных классов поставщика элементов, иерархия наследования которых отражает структуру модели, предоставляет удобную точку диспетчеризации для реализации ясного объектно-ориентированного кода для модели.

Редактирование моделей EMF с помощью команд

До сих пор мы рассматривали отображение моделей EMF с помощью поставщиков содержимого и меток. Среда EMF.Edit поддерживает и другую возможность - редактирование модели с помощью команд. Термин "редактирование" (editing) употребляется в значении "изменение, которое можно отменить", в противоположность простой "записи" (writing) модели.

Области редактирования

Интерфейс EditingDomain среды EMF.Edit позволяет предоставить доступ к модели EMF для ее изменения. Еще один класс реализации EMF.Edit с именем AdapterFactoryEditingDomain работает так же, как поставщики содержимого и меток, делегируя свою реализацию в поставщики элементов (через фабрику ItemProviderAdapterFactory):

Область редактирования

Как видно из рисунка, он также предоставляет доступ к стеку команд, через который выполняются все изменения модели. Область редактирования предоставляет две базовых службы:

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

Чтение и запись поставщиков

Изменение модели

Рассмотрим простой пример изменения модели.

Предположим, что класс Company имеет ссылку типа "один-множество" на класс Department (в компании (Company) есть несколько отделов (Department)). Для того чтобы удалить из компании отдел (то есть реализовать действие редактора Удалить), можно просто написать следующий код:

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

Несмотря на простоту, этот код производит нужное изменение.

Если вместо этого для удаления отдела использовать команду remove среды EMF.Edit (org.eclipse.emf.edit.command.RemoveCommand), то код может выглядеть так:

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

Этот способ удаления отдела имеет несколько преимуществ:

  1. Удаление можно отменить, вызвав метод ed.getCommandStack().undo().
  2. В предположении, что все изменения выполнялись с помощью команд, предоставляемых средой EMF.Edit, можно проверить, не испорчена ли модель (например, для включения меню сохранения), просмотрев команды в стеке команд.
  3. Можно проверить правильность команды (включения меню удаления, например), прежде чем выполнять эту команду с помощью вызова метода cmd.canExecute();

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

Создание команд с помощью области редактирования

В предыдущем примере мы создали команду RemoveCommand с помощью простого вызова new. Этот код превосходно работает, но он не очень универсален (то есть его нельзя использовать повторно), поскольку выполняет весьма специфическое действие - удаление отдела из компании. Если вместо этого требуется, например, создать код для повторно используемого действия delete, способного удалять объект любого рода, то для этого рекомендуется использовать EditingDomain.

Интерфейс EditingDomain помимо других методов содержит метод фабрики команд createCommand(), который можно использовать для создания команд вместо метода new :

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

Для того чтобы создать с помощью этого метода команду, сначала необходимо создать объект CommandParameter и задать в нем параметры команды, а затем вызывать метод create, передав ему нужный класс команды (например, RemoveCommand.class) и параметры.

Вместо того чтобы заставлять клиентов проходить через все это, мы используем соглашение о предоставлении статических удобных методов create() в каждый класс команды. Статический метод create() позволяет создавать и выполнять команду RemoveCommand следующим образом:

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

Как видно из этого примера, небольшое отличие в синтаксисе (RemoveCommand.create() вместо new RemoveCommand) приводит к фундаментальным последствиям. Если раньше передавались три аргумента, то теперь (помимо области редактирования) - только один (то есть удаляемый объект). Заметьте, как этот фрагмент кода может теперь использоваться для удаления объекта любого рода. Делегируя создание команды в область редактирования, мы разрешаем последней заполнять пропущенные аргументы.

Обработка запроса createCommand() областью редактирования

Для того чтобы понять, как это все работает, давайте подробно рассмотрим вызов метода RemoveCommand.create(). Как указывалось ранее, статический метод create() - это именно тот удобный метод, который делегирует в область редактирования примерно следующее:

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

Затем AdapterFactoryEditingDomain принимает запрос и передает его в поставщик элементов, используя стандартный шаблон делегирования (так же, как описанный выше AdapterFactorContentProvider делегировал метод getChildren()):

  public Command createCommand(Class commandClass, CommandParameter commandParameter)
  {
    Object owner = ...  // получить для команды объект владельца
    IEditingDomainItemProvider adapter =
      (IEditingDomainItemProvider)
        adapterFactory.adapt(owner, IEditingDomainItemProvider.class);
    return adapter.createCommand(owner, this, commandClass, commandParameter);
  }

Примечание: если посмотреть на реально существующий метод createCommand(), то обнаружится, что в действительности он намного сложнее. Это связано с тем, что метод обрабатывает, помимо прочего, удаление наборов объектов. Тем не менее, основная идея при этом остается неизменной.

Метод createCommand() использует объект владельца для доступа к поставщику элементов, в который он передает выполнение (то есть владелец используется в вызове adapterFactory.adapt()). Владельцем в нашем примере будет объект компании (то есть предок удаляемого отдела). Для определения владельца область редактирования вызывает метод getParent() из поставщика элементов удаляемого объекта.

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

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

Задание будет выполнено. Однако, существует другой, лучший способ.

Каждый класс поставщика элементов (который является также адаптером EMF) представляет собой расширение соответствующего базового класса ItemProviderAdapter среды EMF.Edit, предоставляющего (помимо прочего) и реализацию по умолчанию для метода createCommand(). Он реализует метод createCommand() для всех предоставляемых средой EMF.Edit стандартных команд, вызывая несколько простых методов (которые используются не только для этой цели), реализованных в производных классах поставщика элементов. Вот пример шаблона разработки шаблонного метода.

Для того чтобы наш пример команды RemoveCommand заработал, необходимо, чтобы CompanyItemProvider реализовал следующий метод:

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

Нетрудно заметить, что этот метод возвращает одну или несколько сущностей (в данном случае - ссылку на отделы (deparments)), которые используются для обращения к потомкам данного объекта. После вызова этого метода реализация по умолчанию метода createCommand() вычислит, какую сущность использовать (если возвращено несколько сущностей), и создаст команду RemoveCommand с правильной сущностью.

Переопределение команд

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

  public class RemoveDepartmentCommand extends RemoveCommand
  {
    public void execute()
    {
      super.execute();
      // выполнить дополнительные действия ...
    }
  }

Эта часть достаточно проста.

Теперь, если вместо операции new RemoveCommand() наш редактор использует статический метод RemoveCommand.create() (который вызывает метод editingDomain.createCommand()), то мы можем легко заменить стандартный класс RemoveCommand нашим классом RemoveDepartmentCommand, подменив метод createCommand() в поставщике элементов:

  public class CompanyItemProvider ...
  {
    ...

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

Фактически, если команда, которую требуется адаптировать, представляет собой одну из предопределенных команд (как RemoveCommand), то замена в этом случае выполняется еще проще, поскольку реализация по умолчанию метода createCommand() передает создание каждой команды в соответствующие удобные методы, как показано ниже:

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

Поэтому создание команды RemoveDepartmentCommand можно было бы упростить, заменяя метод createRemoveCommand(), а не сам метод createCommand():

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

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

Уведомление об изменении модели

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

После того как класс AdapterFactoryContentProvider сконструирован, он регистрирует себя в качестве приемника (то есть org.eclipse.emf.edit.provider.INotifyChangedListener) своей фабрики адаптеров (которая реализует интерфейс org.eclipse.emf.edit.provider.IChangeNotifier). В свою очередь, фабрика адаптеров передается в каждый поставщик элементов, поэтому она может служить для модели центральным уведомителем (notifier) об изменениях. Кроме того, класс AdapterFactoryContentProvider регистрирует (в методе inputChanged()) средство отображения, для которого он поставляет содержимое. Таким образом, этот класс может обновлять свое средство отображения при получении уведомления об изменениях.

На приведенной ниже диаграмме показан путь, по которому изменение модели EMF проходит от объекта этой модели (например, при изменении названия компании) через фабрику адаптеров к средствам отображения модели.

Уведомление об изменении модели

Каждый раз, когда изменяется состояние объекта EMF, во всех адаптерах этого объекта, включая поставщики элементов (в данном случае - CompanyItemProvider), вызывается метод notifyChanged(). Метод notifyChanged() в поставщике элементов определяет, следует ли передавать конкретное уведомление о событии в средство отображения, и если да, то какой тип обновления должен быть результатом этого события.

Для этого указанный метод инкапсулирует уведомления в классе ViewerNotification, представляющем собой простую реализацию интерфейса IViewerNotification. Этот интерфейс расширяет базовый интерфейс Notification:

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

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

Метод notifyChanged() класса CompanyItemProvider выглядит следующим образом:

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

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

Метод fireNotifyChanged() - это соответствующий метод класса ItemProviderAdapter (базового класса всех адаптеров поставщиков элементов), который просто перенаправляет уведомление в фабрику адаптеров [3] . Фабрика адаптеров (уведомитель об изменениях) отправляет полученное уведомление всем своим приемникам (в данном примере - только поставщику содержимого средства отображения дерева). Наконец, поставщик содержимого обновляет средство отображения в соответствии с уведомлением.

Составные фабрики адаптеров

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

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

Объединение фабрик адаптеров

Фабрика адаптеров ComposedAdapterFactory используется для предоставления общего интерфейса другим фабрикам адаптеров, в которые она просто делегирует свою реализацию.

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

  model1AdapterFactory = ...
  model2AdapterFactory = ...

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

  myContentProvider = new AdapterFactoryContentProvider(myAdapterFactory);
  ...

Применение генератора кода EMF.Edit

Примечание: подробное описание этапов создания (генерации) модели EMF, а также редактора EMF.Edit можно найти в документе Учебник: Генерация модели EMF.

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

Генератор EMF.Edit создает полностью работающие встраиваемые модули, которые включают следующие компоненты:

  1. ItemProviderAdapterFactory
  2. ItemProvider, по одному для каждого класса модели
  3. Editor
  4. ModelWizard
  5. ActionBarContributor
  6. Plugin
  7. plugin.xml
  8. Icons

После того как редактор создан, он должен запуститься. Редактор откроется, но может работать не так, как ожидалось (то есть настройки по умолчанию, установленные генератором, могут не соответствовать вашей модели). Однако, для быстрого запуска базового рабочего редактора должно быть достаточно нескольких небольших изменений созданного кода.

Теперь более подробно рассмотрим несколько наиболее интересных генерируемых классов.

ItemProviderAdapterFactory

Генерируемый класс ItemProviderAdapterFactory - это простой производный класс генерируемого класса AdapterFactory, который был получен при генерации кода для вашей модели EMF.

Примечание: генерированная фабрика адаптеров EMF создает адаптеры, передавая выполнение в метод create(), соответствующий определенному типу; причем этот метод должен быть перегружен производными классами (например, классом ItemProviderAdapterFactory). Фабрика адаптеров EMF (например, ABCAdapterFactory) использует для эффективной передачи выполнения другой генерированный класс (ABCSwitch).

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

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

Если вместо этого используется шаблон единого адаптера, то фабрика адаптеров, кроме того, отслеживает экземпляр единого адаптера и возвращает его для каждого вызова:

  protected DepartmentItemProvider departmentItemProvider;

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

Классы ItemProvider

Для каждого класса модели генерируется соответствующий класс поставщика элементов. Генерируемые поставщики элементов входят во все интерфейсы, которые требуются для поддержки стандартных средств отображения, команд и окна свойств:

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

Если класс модели является корневым (то есть для него не существует явного базового класса), то генерируемый для него поставщик элементов будет расширением базового класса поставщика элементов EMF.Edit, который называется ItemProviderAdapter:

  public class EmployeeItemProvider extends ItemProviderAdapter ...

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

  public class EmployeeItemProvider extends PersonItemProvider ...

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

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

  1. Идентификация потомков и предка
  2. Настройка дескрипторов свойств
  3. Передача уведомлений об "интересных" событиях.

Editor и ModelWizard

Генерируемые классы Editor и ModelWizard указывают, как собрать вместе другие генерированные фрагменты кода и стандартные компоненты JFace, чтобы получился работающий редактор.

Класс ModelWizard позволяет создавать новые ресурсы типа модели. Однако, если у вас уже есть ресурс, созданный другим способом, его можно импортировать в рабочую область и запустить редактор на этом ресурсе, полностью пропустив ModelWizard.



[1] Notifier - это базовый интерфейс EMF для объектов, которые могут регистрировать адаптеры и отправлять им уведомления. Он расширяется интерфейсом EObject - базовым интерфейсом для всех объектов модели.

[2] В действительности управление фабриками адаптеров EMF наследуется, поэтому на любом уровне модели для управления производными классами можно выбрать базовый адаптер. EObject представляет собой особый случай.

[3] Кроме фабрики адаптеров, которая действует как уведомитель об изменениях (notifier) для средств отображения, ItemProviderAdapter может напрямую отправлять уведомления в приемники (listeners), которые также вызываются в ItemProviderAdapter.fireNotifyChanged().