Сообщений 0    Оценка 16 [+0/-1]         Оценить  
Система Orphus

Еще раз о разработке плагинов Eclipse

Автор: Александр Цимбал
Источник: RSDN Magazine #1-2008
Опубликовано: 17.07.2008
Исправлено: 15.04.2009
Версия текста: 1.0
Структура плагинов и их расположение
Управление компонентами
Класс плагина и его использование
Класс платформы Eclipse
Дескриптор plugin.xml плагина
Точки расширения (extension points)
Создание точки расширения
Использование точки расширения
Некоторые стандартные точки расширения
Действия (Actions)
Создание и добавление перспектив и представлений
Другие часто используемые точки расширений
Заключение

Поскольку разработка плагинов – главная часть создания приложений для платформы Eclipse, то разработчики самой платформы приложили огромное количество усилий, чтобы сделать этот процесс максимально простым. Эксперты для создания компонентов Eclipse (плагинов (plug-ins) в терминах «классического» Eclipse или бандлов (bundles) в терминах OSGi (Open Services Gateway interface)) с «технической точки зрения» почти тривиальны. Но только при одном условии – если разработчик имеет перед глазами достаточно «общую» картину структуры платформы и имеет отчетливое представление о ее наиболее часто используемых возможностях. Именно создание такого представления занимает большую часть времени, которое должен потратить начинающий разработчик компонентов. Данная статья имеет целью способствовать сокращению этого времени. В ней не ставится задача рассмотреть даже только важнейшие детали – размер статьи не позволит, деталей слишком много. Без знания деталей (например, где располагаются компоненты, какую структуру имеют каталоги плагинов – в «естественном виде» или в виде jar-файлов, какие имена они (каталоги или jar-файлы должны иметь) и многого другого) – плагин не создать. Но излишние детали на определенной стадии знакомства с технологией подобны даже не деревьям, скрывающим лес, а листьям на этих деревьях. Здесь делается попытка вести разговор о лесе – это очень упрощает жизнь при первом знакомстве с новой местностью.

Предполагается, что читатель (на уровне пользователя среды Eclipse) знаком с такими понятиями, как workspace, представление (view), редактор (editor), перспектива (perspective) и пр. Хорошим введением для программистов Eclipse является статья «Проект Eclipse» (http://www.rsdn.ru/article/devtools/eclipse.xml?print).

Структура плагинов и их расположение

Итак, плагин Eclipse.

Как любой компонент, плагин:

  1. «Живет» в определенной среде, берущей на себя управление цикла жизни.
  2. Содержит информацию о себе – как о своих возможностях (информация на уровне типа), так и о реальном существовании (информация на уровне экземпляра, instance).
  3. Узнает (динамически) о существовании других компонентов.
  4. Взаимодействует с другими компонентами – как «статически» (на уровне типов), так и «динамически» (на уровне экземпляров).
  5. Может иметь состояние, которое, в общем случае, нужно (и можно) сохранять.
  6. Имеет определенное разработчиком поведение – назначение, функциональность, свойства.
  7. Должен иметь возможность динамически менять свое поведение.
  8. Должен иметь возможность создания иерархии (динамической системы взаимодействующих компонентов) для реализации требуемой прикладной функциональности для самых различных задач.

Структура среды управления компонентами и самих компонентов вытекают из этих задач.

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

В базовом варианте при развертывании платформы Eclipse (и последующем добавлении/удалении плагинов для этого экземпляра платформы) доступные плагины должны находиться в фиксированном (относительно каталога инсталляции) месте, конкретно, в подкаталогах features и plugins. Эти компоненты (в виде подкаталогов или в виде jar-файлов с той же структурой каталогов) потенциально доступны на уровне самой платформы, т.е. всех приложений, выполняемых под данным экземпляром платформы.

Этой жесткой схеме может быть придан элемент гибкости за счет использования «ссылок» – когда подкаталоги/jar-архивы плагинов находятся в произвольных местах, а на уровне платформы хранятся ссылки на них – текстовая информация в файлах с расширением .link, находящихся в каталоге links данного экземпляра платформы. Это позволяет, например, использовать одну и ту же копию реализации плагина для разных экземпляров установленных платформ Eclipse.

Это далеко не всегда удобно и с точки зрения визуального представления среды, и с точки зрения потребных для исполнения приложений ресурсов: чем больше компонентов, тем медленнее запускается платформа, тем больше нужно памяти для загрузки используемых компонентов. В результате обычной является ситуация, когда для разных целей используются несколько различных инсталляций Eclipse, что, в свою очередь, порождает свои сложности (настройка, обновление и т.д.). С помощью довольно изощренного «скриптового программирования» можно «менять настройки» единственного экземпляра платформы (см., например, очень интересную статью http://constantiner.blogspot.com/2006/03/eclipse.html, правда, она отнюдь не для начинающих, но зато в ней много полезных «точек входа» для начала рассмотрения различных тем).

Тем не менее, важно понимать, что большая часть информации о плагинах (их код, XML-дескрипторы, статические ресурсы) «замкнуты» на экземпляр самой платформы Eclipse.

В то же время часть информации о плагине, а именно, их состояние – в том числе настройки (preferences), привязаны не к установленному экземпляру платформы, а к используемому workspace (подкаталог /.metadata/.plugins). Поэтому workspaces имеют отношение не только к прикладным проектам. Впрочем, Eclipse предоставляет возможность «разделения» такой информации между различными workspaces.

Управление компонентами

До появления версии Eclipse 3.1 управление плагинами (регистрация, загрузка и выгрузка, организация связей между ними и пр.) осуществлялось нестандартными средствами, специфическими для Eclipse. На смену этому проприетарному механизму пришло использование открытых стандартов, в данном конкретном случае – OSGi (Open Services Gateway interface). С точки зрения OSGi плагин является «сборкой (bundle)» этого стандарта. Сборка является основной «единицей управления».

Поскольку OSGi не интересует прикладная функциональность компонента, оформленного в виде сборки, то описание сборки выполнено в виде специального XML-документа. Этот документ (файл MANIFEST.MF в подкаталоге META-INF). Содержимое манифеста может выглядеть примерно так:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: My favorite Plug-in
Bundle-SymbolicName: org.my_plugin; singleton:=true
Bundle-Version: 1.0.0
Bundle-ClassPath: my_plugin.jar
Bundle-Activator: org.my_plugin.demo.MyActivator
Bundle-Vendor: Me
Require-Bundle: org.eclipse.ui,
 org.eclipse.core.runtime
Eclipse-LazyStart: true
Export-Package: org.my_plugin.demo.subpackage;
  uses:="org.eclipse.swt.graphics,
   org.eclipse.jface.viewers,
   org.eclipse.ui.part,
   org.eclipse.swt.widgets"

Термины «Bundle» и «Package» говорят сами за себя – в манифесте объявлены свойства сборок OSGi и (не всегда) пакетов Java, которые должны быть доступны другим сборкам. Bundle-SymbolicName позволяет задать уникальное имя сборки. Name и Vendor – простые текстовые описания.

Тег Require-Bundle перечисляет сборки, которые используются данной сборкой и, соответственно, должны присутствовать при исполнении данного компонента. При работе с дескриптором компонента среда Eclipse (точнее, загрузчик плагинов) определяет наличие всех необходимых сборок, и, если некоторая сборка не найдена, то возбуждается исключение. При этом делается соответствующая запись в журнал (log), а текущая сборка не загружается. Если отсутствие сборки из списка Require-Bundle не является фатальным для загрузки данного компонента, то такую сборку можно пометить как необязательную (resolution:=optional). Среда Eclipse в случае необходимости позволяет разработчику создать и использовать свой собственный загрузчик компонентов.

Важным тегом является тег Eclipse-LazyStart (в Eclipse 3.1 тег назывался Eclipse-AutoStart, который, начиная с Eclipse 3.2, объявлен устаревшим). Значение true говорит о том, что загрузка самого компонента может быть отложена до того момента, когда компонент действительно будет нужен.

О теге Bundle-Activator будет сказано немного ниже.

Манифест является первым XML-дескриптором компонента. Очевидно, что в нем нет ничего, имеющего отношения к функциональности, настройкам компонента, его взаимодействию с другими компонентами и аспектами визуального представления, и многому другому. Эта дополнительная информация находится в другом дескрипторе компонента – файле с именем plugin.xml. Его основное назначение – описать использование «точек расширения» (extension points) Eclipse, о чем разговор впереди.

Работа с дескрипторами компонентов начинается при старте платформы Eclipse. Загрузчик плагинов просматривает каталоги plugins (и/или links) в поисках установленных компонентов, и для каждого компонента анализирует файлы MANIFEST.MF и plugin.xml. При этом в памяти строится структура связей сборок и их описаний (реестр плагинов), но загрузки кода компонентов не происходит (структура управления плагинами занимает в памяти несоизмеримо меньше места, чем «обычный» набор плагинов). Загрузка плагина и его активация (забегая немного вперед – вызов callback-метода start() класса плагина) выполняется тогда, когда это действительно необходимо.

Надо сказать, что управление загрузкой плагинов претерпевало значительные изменения, и связано это, в первую очередь, не с самой платформой Eclipse, а с развитием стандарта OSGi. До использования OSGi, т.е. в Eclipse 2.x, разработчик плагинов в случае, когда необходимо было загрузить плагин до его использования, т.е. при старте самой платформы, должен был использовать специальную точку расширения и реализовывать интерфейс org.eclipse.ui.IStartup. В настоящий момент такой подход использовать не нужно – все необходимые настройки задаются на уровне тега Eclipse-LazyStart (или тега Bundle-ActivationPolicy OSGi). Полнофункциональная поддержка Eclipse-LazyStart и Bundle-ActivationPolicy реализована в Eclipse 3.3. Разработчикам компонентов имеет смысл подробно ознакомиться с возможностями тега Eclipse-LazyStart.

Класс плагина и его использование

Класс плагина (он же класс-активатор плагина) – это класс, экземпляр которого создается средой при загрузке плагина. Этот класс должен реализовывать интерфейс org.osgi.framework.BundleActivator:

package org.osgi.framework;

public interface BundleActivator 
{
  public void start(BundleContext context) throws Exception;
  public void stop(BundleContext context) throws Exception;
}

Среда вызывает метод start() этого интерфейса сразу после загрузки плагина и метод stop() непосредственно перед его выгрузкой. Полное имя класса плагина обычно указывается в манифесте (MANIFEST.MF), в теге Bundle-Activator.

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

На практике класс плагина (точнее, сам плагин – не обязательно класс его активатора) должен уметь гораздо больше, чем выполнять некоторые действия при инициализации (активизации) и деинициализации, поэтому платформа Eclipse предлагает в готовом виде небольшую иерархию классов, которые реализуют данный интерфейс. «Корнем» этой иерархии является класс org.eclipse.core.runtime.Plugin, и именно на основе этого класса разработчик создает класс плагина (в первую очередь это касается плагинов, не имеющих визуального графического представления):

public abstract class Plugin implements BundleActivator 
{
...
}

Помимо конструктора и методов управления циклом жизни, класс Plugin содержит несколько очень полезных методов:

Если же разработчик собирается создать плагин с графическим интефейсом, то обычно используется другой класс – AbstractUIPlugin:

public abstract class AbstractUIPlugin extends Plugin
{   
  ...
}

Основным отличием этого класса от класса Plugin является поддержка дополнительных хранилищ и реестров, в той числе для графической информации, а также автоматическое сохранение настроек плагине при ее остановке.

Запущенный экземпляр платформы Eclipse выполняется под управлением одной виртуальной машины Java (JVM). Что касается компонентов, то для загрузки каждого плагина используется свой загрузчик классов (classLoader). Такое архитектурное решение приводит к тому, что для использования некоторых «внешних» библиотек, которые должны иметь доступ к ресурсам плагина (например, log4j), необходимо обеспечить специальный механизм загрузки классов. Этот механизм называется «buddy loading», и управление им осуществляется с помощью задания специальных тегов манифеста плагина – Eclipse-BuddyPolicy и Eclipse-RegisterBuddy (реализовано начиная с Eclipse 3.3).

Класс платформы Eclipse

Поскольку управление компонентами берет на себя среда, то разработчику плагина часто нужно получить информацию о параметрах настройки и исполнения самой среды. Такой информацией может быть список установленных плагинов, каталоге установке, менеджере заданий (job manager, задания – объекты Eclipse, являющиеся, по сути, высокоуровневыми аналогами потоков Java), связях между плагинами и многое другое. Для этого су ществует специальный класс, содержащий большое количество полезных сервисных функций – org.eclipse.core.runtime.Platform. Как и следовало ожидать, все методы и поля этого класса являются статическими.

Дескриптор plugin.xml плагина

Второй основной дескриптор компонента (наряду с манифестом) – xml-дескриптор, который должен находиться в файле plugin.xml. Этот дескрптор также анализируется загрузчиком плагинов Eclipse до того, как будет загружаться и исполняться код плагина. Основная задача этого дескриптора – описать связи плагинов, которые создаются с помощью механизма «точек расширения». Точки расширения кратко будут рассмотрены ниже, здесь же пока нужно запомнить следующее: использование данных дескрипторов возможно без загрузки кода (и других ресурсов) плагина. Оказывается, что под эту категорию попадают не только явно «статические» связи между компонентами, но и информация, которая в программировании обычно используется на уровне кода компонентов (а не их дескрипторов).

Eclipse задает специальный «язык программирования» на уровне XML, который очень широко используется, например, при задании режимов доступности отдельных элементов, их видимости для пользователей. При этом можно выполнять проверки на уровне типов компонентов (является ли класс экземпляром некоторого класса), их состояния («установлен», «активирован» и т.д.), можно строить логические выражения с использованием операторов and, not и or. Например, следущий фрагмент дескриптора позволяет сделать доступным элемент интерфейса пользователя (пункт меню, выпадающее меню и т.п.) только в том случае, если выделенным элементом при работе приложения (в том числе и самой IDE Eclipse) является файл (один из видов «ресурсов» Eclipse, наряду с папками и workspace), причем имя файла имеет расширение .java:

...
  <enablement>
    <and>
      <objectClass name="org.eclipse.core.resource.IResource"/>
      <objectState name="name" value="*.java"/>
    </and>
  </enablement>  

На практике разработчик довольно редко работает с дескрипторами компонента «вручную» – PDE содержит графические эксперты, которые существенно облегчают работу и снижают вероятность ошибок.

Впрочем, понимание структуры и назначения дескриптора plugin.xml появится только после знакомства с основами «точек расширения» Eclipse.

Точки расширения (extension points)

Тема точек расширения – ключевая для создания расширений платформы Eclipse. Использование или создание точки расширения, с одной стороны, простая задача, так как ее решение четко детерминировано и автоматизировано. С другой стороны, создание универсального компонента для «широкого использования», который, к тому же, эффективно использует вычислительные ресурсы – непростая задача при использовании какой угодно компонентной технологии.

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

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

Создание точки расширения

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

Возьмем, к примеру, представления Eclipse (связанные с перспективой, но это не так уж важно на этом этапе). Есть, условно говоря, «список представлений», а также код, который этими представлениями управляет, причем вполне единообразно. Это немного похоже на контейнеры стандартных библиотек Java или C++ (конечно, контейнеры не связаны с визуальным представлением информации, но это в данном случае непринципиально). При этом различные представления являются компонентами «различных типов», хотя и сводимых к общему типу «представление».

Напрашивается стандартный объектно-ориентированный подход, а именно: создать «базовый класс представления» со стандартной базовой функциональностью, предоставить возможность программисту создавать производные от него классы и переопределять виртуальные методы (это делают сами объектно-ориентированные языки программирования), а также создать класс для управления некоторой совокупностью экземпляров абстрактных «представлений» (на практике – ссылок на экземпляры реальных классов, непосредственно или опосредованно производных от «базового» представления). Ниже приведен псевдо-код для иллюстрации:

interface IBaseView 
{
  void show();
  void hide();
}

abstract class BaseView implements IBaseView { ... }

class MyViewOne extends BaseView 
{
  MyViewOne() { ... }
}

class MyViewTwo implements IBaseView 
{
  MyViewTwo() { ... }
}

class MyViewThree extends MyViewOne 
{
  MyViewThree() { ... }
}

class SetOfViews 
{
  private List<IBaseView> views;
  ...
  void  addView(IBaseView view)
  { 
    views.add(view);
  }

  void  removeView(IBaseView view) 
  {
    views.remove(view);
  }

  void showAll()
  {
    ...
  }

  void hideAll()
  {
    ...
  }
}

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

  1. Если класс (подобно MyViewTwo) создан как реализация интерфейса, это позволяет разработчику класса BaseView свободно менять его функциональность, а также «развязывает руки» создателю классу MyViewTwo. Но это работает до тех пор, пока интерфейс IBaseView неизменен. Любые изменения интерфейса приведут к необходимости переписывания реализующих этот интерфейс классов.
  2. Если класс (подобно MyViewOne) построен на базе класса (BaseView, в данном случае), то изменение кода этого класса (добавление новых полей или методов) не приведет к потере работоспособности построенных на его основе производных классов. Но при этом новые классы будут излишне «завязаны» на функциональность уже существующих классов, что не всегда удобно.
  3. Набор приведенных выше классов «неполон» в том смысле, что эти классы не содержат код создания экземпляров «представлений» – где-то нужно вызывать конструкторы классов MyViewOne, MyViewTwo и т.д. Какой фрагмент приложения будет создавать эти экземпляры, а также вызывать «метод регистрации» - addView()?
  4. Когда нужно выполнять загрузку классов наших «представлений»?
  5. Как разработчик новых «предcтавлений» узнает об уже имеющихся представлениях (например, разработчик MyViewThree должен знать о существовании и возможностях MyViewOne)?
  6. Как обеспечить возможность настройки поведений классов без изменения их кода (с последующей перекомпиляцией)?

Для решения этих и других проблем и используется механизм точек расширения Eclipse.

Создатель точки расширения должен сделать следующее:

  1. Решить, какая функциональность может быть расширена разработчиками тех плагинов Eclipse, которые используют данный плагин. Примерами такой «расширяемой» функциональности являются запуск отдельного приложения (реализованного как плагин Eclipse), управление графическими сущностями Eclipse или управление основными и выпадающими меню.
  2. Сообщить о существовании точки расширения и правилах ее использования как самой среде Eclipse (поскольку именно она управляет циклом жизни плагинов), так и разработчикам расширений, которые будут использовать данную точку.
  3. Написать код управления расширениями. Этот код, в частности, должен загружать необходимые расширения, создавать их экземпляры и «регистрировать» эти экземпляры для дальнейшего использования в программе.
  4. Использовать загруженные расширения (например, запустить новое приложение, отобразить в нужном месте новое представление или редактор, поместить в меню новую команду или добавить новую кнопку в панель кнопок управления).

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

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

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

Во-вторых, нужно всегда помнить, что для одной точки расширения может существовать множество различных реализаций этого расширения, созданных разными авторами. На практике это приводит к использованию различного рода контейнеров ссылок и/или механизма listener'ов. Читателю, который не знаком с механизмом listener'ов, реализованного, например, в рамках технологии JavaBeans, необходимо ознакомиться с этим шаблоном организации взаимодействия компонентов.

В-третьих, нужно полагать, что расширения представляют собой плагины Eclipse, т.е. для их загрузки используются различные загрузчики классов, что не позволяет полагаться на механизм создания экземпляров с помощью вызова Class.forName(). Для создания экземпляра классов (и загрузки, если необходимо, плагина) нужно использовать специальные методы – IConfigurationElement.createExecutable() или IConfigurationElement.createExecutableExtension(). Типичный код может выглядеть примерно так:

  String ext_point_id = “...”;
  ...
  // получение ссылки на реестр плагинов для экземпляра среды
  IPluginRegistry plug_reg = Platform.getPluginRegistry();

  // поиск точки расширения в реестре плагинов
  IExtensionPoint ext_point = plug_reg.getExtensionPoint(ext_point_id);

  // получение зарегистрированных расширений для данной точки
  IExtension[] exts = ext_point.getExtensions();

  //получение конфигураций расширений, создание экземпляров и
  // их использование
  for (int i = 0; i < exts.length; i++)
  {
    IConfigurationElement[] elems = exts[i].getConfigurationElements();

    for (int j = 0; j < elems.length; j++)
    {
      Object ext = elems[j].createExecutableExtension(“имя_класса”);
      ...
    }
    ...
  }    

«имя_класса» - это не обязательно имя самого требуемого класса. Очень часто это имя фабрики класса.

В-четвертых, нужно помнить о потокобезопасности кода, в том числе и о том, чтобы не использовать средства защиты на уровне потоков тогда, когда в этом нет необходимости (например, при работе с SWT обращения к компонентам (визуальным сущностям) выполняются всегда из одного потока – UI-потока).

В-пятых, нужно всегда помнить об обработке исключительных ситуаций, особенно с учетом того, что код плагинов выполняется под управлением среды Eclipse. Для вызова методов плагинов, связанных с возможным возникновением исключений, очень удобно использовать специальный интерфейс org.eclipse.core.runtime.ISafeRunnable:

public interface ISafeRunnable 
{
  public void handleException(Throwable exception);
  public void run() throws Exception;
}

Вызов метода run() реализации этого интерфейса часто выполняется с помощью вызова

Platform.run (runnable_instance);

При возникновении исключения выполняется код callback-метода handleException().

Использование точки расширения

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

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

По сути, создание расширения заключается в реализации callback-методов, которые будут вызываться теми плагинами, которые и определили данную точку. Поскольку точки расширения «ведут себя» по-разному, удобно совместить знакомство с их использованием с рассмотрением некоторых стандартных точек расширения Eclipse – теми, которые наиболее часто используются прикладными программистами.

Некоторые стандартные точки расширения

Общий список стандартных точек расширения Eclipse можно получить различными способами. Можно обратиться к встроенной справочной системе (Help->Help Contents->Platform Plug-in Developer Guide->Reference->Extension Point Reference. Другой способ получения той же информации, уже на стадии разработки плагина, – использование эксперта создание плагинов (закладка extensions эксперта, кнопка Add...).


Рисунок 1. Выбор точки расширения.

К сожалению (или к счастью?), стандартных точек расширения очень много, и начинающему разработчику плагинов трудно выбрать наиболее часто используемые. Ниже мы рассмотрим, на какие точки следует обратить внимание в первую очередь и как их нужно использовать.

Стандартные точки расширения Eclipse разбиты на несколько групп. Наиболее важной (на начальной стадии разработки плагинов) являются точки группы Workbench – их (но не только их) идентификаторы начинаются с org.eclipse.ui. Начать проще всего с «действий» (Actions).

Действия (Actions)

Действия Eclipse тесно связаны с механизмом доступа к этим действиям, но их не следует смешивать. Action – отдельная сущность, не тождественная пунктам меню или кнопкам панелей инструментов. Одно и тоже действие может быть сделано доступным различными способами.

Действие Eclipse реализуется с помощью нескольких классов и интерфейсов. Сделано это вследствие того, что функциональность этого объекта явно делится на визуальную («интерфейс пользователя») и «неотображаемую» часть. Такое разделение интерфейса (задаваемого на уровне дескриптора плагина) и собственно кода позволяет отображать «средства доступа» к действию (меню, кнопки) без необходимости загрузки кода плагина, реализующего необходимые операции. Кроме того, на основе информации в дескрипторе можно управлять видимостью и доступностью действий.

Основой действия (как элемента Eclipse) является интерфейс org.eclipse.jface.action.IAction и реализующий его абстрактный класс Action.

Программист же, создавая плагин, использует другой интерфейс (точнее, один из нескольких интерфейсов), который отвечает за «клиентскую» функциональность. Основным из этих клиентских интерфейсов является интерфейс org.eclipse.ui.IActionDelegate:

public interface IActionDelegate 
{
    public void run(IAction action);
    public void selectionChanged(IAction action, ISelection selection);
}

Метод run() должен содержать выполняемый код для действия. Он вызывается при нажатии на соответствующую кнопку или при выборе пункта меню (если действие доступно). Метод selectionChanged() сигнализирует о том, что при интерактивной работе в среде Eclipse произошло изменение выбранного элемента интерфейса пользователя. Вместо непосредственной реализации этого интерфейса разработчик плагина может использовать (в качестве базового класса) абстрактный класс org.eclipse.ui.actions.ActionDelegate.

На практике при создании расширений обычно используется не IActionDelegate, а производные от него интерфейсы: IWorkbenchWindowActionDelegate (при работе с главным меню и панелью кнопок рабочей среды (workbench)), IObjectActionDelegate (при работе с контекстным меню), IViewActionDelegate (при работе с представлениями, view) и IEditorActionDelegate (при работе с редакторами, editor). Эти производные интерфейсы полнее используют специфику выполнения действий в том или ином контексте.

Обычная задача программиста – создать класс, реализующий один из этих интерфейсов, объявить его в дескрипторе плагина и сопоставить эту реализацию с кнопками и/или пунктами меню. Продемонстрируем это на примере действия на уровне главного окна среды Eclipse (т.е. с реализацией IWorkbenchActionDelegation)

Действие Eclipse задается с помощью довольно большого набора свойств (элементов и атрибутов, в терминах структуры точки расширения). Для его задания используется тег <action>:

<action 
  class = "my.demo.action.MyWBActionDelegateImpl"
  icon = "icons/sample.gif"
  id = "my.demo.action.id"
  label = "&Do something"
  menubarPath = "my.demo.workbenchMenu/content"
  toolbarPath = "Normal/additions"
  tooltip = "Сделать что-то">
</action> 

Смысл атрибутов понятен: class задает имя класса, реализующего интерфейс IWorkbenchActionDelegate, icon – используемую графическую пиктограмму, id – уникальный идентификатор действия (он нужен для связи с объектом IAction), label – надпись, сопоставленная с элементом доступа к действию (элемент меню или кнопка панели инструментов), tooltip – текстовая подсказка. Атрибуты menubarPath и toolbarPath определяют место в меню и панели инструментов, где появится соответствующий элемент управления. Обратите внимание, что меню пока не определено.

Реализация интерфейса IWorkbenchActionDelegate может быть следующей:

package my.demo.action;

public class MyWBActionDelegateImpl implements IWorkbenchWindowActionDelegate 
{

  private IWorkbenchWindow window;  // рабочая среда
  
  public void dispose() 
  {
    this.window = null;
  }

  public void init(IWorkbenchWindow window) 
  {
    this.window = window;
  }

  public void run(IAction action) 
  {

    if (window == null)
      return;
    IWorkbenchPage page = window.getActivePage();
    if (page == null)
      return;
    ...
  }

  public void selectionChanged(IAction action, ISelection selection) { }
}

Следующий шаг – привязка данного действия к элементу меню. Связка «action – menu» является частью тега <actionSet>:

<actionSet
description="Это описание"
  id="my.demo.workbenchActionSet"
  label="Му ActionSet"
  visible="true">
  <menu
    id="my.demo.workbenchMenu"
    label="My &Actions"
    path="additions">
    <groupMarker
      name="content">
    </groupMarker>
    <separator
      name="additions">
    </separator>
  </menu>
  <action 
    class="my.demo.action.MyWBActionDelegateImpl"
    icon="icons/sample.gif"
    id="my.demo.action.id"
    label="&Do something"
    menubarPath="my.demo.workbenchMenu/content"
    toolbarPath="Normal/additions"
    tooltip="Сделать что-то">
  </action> 
</actionSet>

Здесь не место подробно обсуждать особенности свойств меню – смысл ясен и так.

Ну, и, наконец, привязка actionSet'а к точке расширения. Эта точка расширения, как сейчас уже можно догадаться, называется org.eclipse.ui.actionSets:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
...
  <extension
    point="org.eclipse.ui.actionSets">
    <actionSet
      description="Это описание"
      id="my.demo.workbenchActionSet"
      ...
    </actionSet>
  </extension>
  ...
</plugin>

Вот и все, что нужно сделать для реализации расширения для стандартной точки org.eclipse.ui.actionSets. Эксперты PDE позволяют полностью отказаться от работы «вручную» с дескриптором плагина – файлом plugin.xml.

При работе с контекстным меню используется примерно тот же подход, но будет задействована другая точка расширения, а реализовать нужно немного другой интерфейс – не IWorkbecnchActionDelegate, а IObjectActionDelegate:

public interface IObjectActionDelegate extends IActionDelegate 
{
  public void setActivePart(IAction action, IWorkbenchPart targetPart);
}

Наличие метода setActivePart() позволяет получить (и сохранить) ссылку на активное в настоящее время представление или редактор (Part – общий термин Eclipse для обозначения «окон» в составе перспективы). Следовательно, реализация этого метода в классе для данного действия может выглядеть так:

public class MyObjectActionDelegateImpl implements IObjectActionDelegate 
{

  private IWorkbenchPart activePart;
  
  public MyObjectActionDelegateImpl() { }

  public void setActivePart(IAction action, IWorkbenchPart targetPart) 
  {
    this.activePart = targetPart;
  }

  public void run(IAction action) 
  {
    MessageDialog.openInformation (targetPart.getSite().getShell(), 
    "Меню My Action,", "действие " + getClass().getName());
  }

  public void selectionChanged(IAction action, ISelection selection) { }
}

Теперь осталось сопоставить эту реализацию действия с элементом контекстного меню и с точкой расширения org.eclipse.ui.popupMenus:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
  ...
<extension point="org.eclipse.ui.popupMenus">
  <objectContribution
    adaptable="true"
    id="my.demo.popupMenu"
    objectClass="org.eclipse.core.resources.IResource">
    <action
      class="my.demo.action.MyObjectActionDelegateImpl"
      enablesFor="+"
      id="my.demo.action.object_id"
      label="My objectAction"
      menubarPath="my.demo.popupSubMenu/content"
      tooltip="Действие по контекстному меню!">
    </action>
    <menu
      id="my.demo.popupSubMenu"
      label="My Label"
      path="additions">
      <groupMarker
        name="content">
      </groupMarker>
      <separator
        name="additions">
      </separator>
    </menu>
  </objectContribution>
</extension>

</plugin>

На что здесь следует обратить внимание? Несмотря на то, что работа с точкой расширения popupMenus очень похожа на работу с actionSets, структура точек расширения разная (тег <actionSet> в одном случае и <objectContribution> – в другом). Различаются также и атрибуты элемента <action>.

При работе с точкой popupMenus нет смысла говорить о панели инструментов, зато важно учесть, что контекстное меню при нажатии пользователем правой кнопки мыши должно появляться не всегда, а только при определенных условиях (например, в зависимости от типа выбранного элемента пользовательского интерфейса, числа выбранных элементов и многого другого). Кроме того, контекстное меню часто создается не «статически» (точнее, не только статически), на уровне дескриптора плагина – это потребовало бы реализации весьма изощренной логики с использованием XML – но и «динамически» (при работе программы).

В данном примере на параметры отображения контекстного меню влияют два атрибута:

  <objectContribution
    ...
    objectClass="org.eclipse.core.resources.IResource">
    <action
      enablesFor="+"
      ...
    </action>
    ...
  </objectContribution>

Атрибут objectClass элемента objectContribution (привыкайте к терминам схемы XML-дескрипторов точек расширений!) говорит о том, что контекстное меню может появиться в представлении (или редакторе) только в том случае, если выбранным элементом в «окне» является папка или файл (например, в окне Navigator). (к ресурсам Eclipse относятся также проекты и «рабочие пространства» – workspaces). Атрибут enablesFor позволяет определить доступность действия в зависисмости от числа выделенных в представлении элементов (например, файлов). Значение «+» говорит о том, что действие будет доступно, если выделен один или более файлов (точнее, ресурсов). Для управления видимостью элементов меню и доступностью действий предусмотрено несколько атрибутов и элементов, которые при умелом использовании позволяют обеспечить довольно гибкое управление. Возможности по управлению даже немного «избыточны» – в том смысле, что многие действия могут быть реализованы различными способами, каждый из которых может быть более (или менее) удобен в разных ситуациях. Обратите внимание на атрибут nameFilter элемента <objectContribution>, дочерний (для objectContribution) элемент <visibility> с его многочисленными дочерними элементами, а также на дочерние (для элемента <action>) элементы <selection> и <enablement>.

К теме действий относятся также реализация точeк расширения (связанных с поддержкой Actions) отдельно для представлений и редакторов Eclipse. Специфика в данном случае состоит в том, что эти панели интерфейса могут иметь, наряду с контекстным меню, еще и панели инструментов, и выпадающее (pull-down) меню. Откройте, например, перспективу Java Browsing и посмотрите на представление Members.

Для учета специфики представлений и редакторов точек расширения (org.eclipse.ui.viewActions и org.eclipse.ui.editorActions), соответственно, используется не элемент <objectContribution> (как в предыдущем примере), а элементы <viewerContribution> и <editorContribution>. Основное концептуальное отличие состоит в том, что для точки расширения popupMenus контекстное меню зависит от типа, выделенного в представлении элемента, а для viewActions – от типа самого представления. Тип представления задается с помощью атрибута targetID элемента <viewerContribution>. Правда, возникает вопрос, а где взять идентификаторы типов «стандартных» (т.е. не созданных разработчиком) представлений и редакторов? На самом деле это проблема (для разных версий Eclipse). Существует несколько способов получения идентификаторов, но все они связаны с созданием собственного кода или даже собственной утилиты. На эту тему вполне можно написать отдельную статью. В простейшем случае получить идентификатор конкретного представления можно, работая с представлением и используя класс org.eclipse.ui.internal.PartSite.

Для версии 3.3 идентификаторы можно найти в дескрипторе plugin.xml (в jar-файле для org.eclipse.jdt.ui, в моей версии Eclipse это файл org.eclipse.jdt.ui_3.3.1. r331_v20070906.jar). В этом файле информация об идентификаторах стандартных элементов интерфейса представлена в следующем виде:

<extension id="javaeditor" point="org.eclipse.ui.editors">
  - <editor name="%CompilationUnitEditorName" default="true" 
  ...
  id="org.eclipse.jdt.ui.CompilationUnitEditor">
  </editor>
  - <editor name="%ClassFileViewerName" default="true"
  ...
  id="org.eclipse.jdt.ui.ClassFileEditor">
  </editor>
  
</extension>
<extension point="org.eclipse.ui.views">
  <category name="%Browsing.viewCategoryName"
    id="org.eclipse.jdt.ui.java.browsing" /> 
  <view name="%Browsing.projectsViewName"
    icon="$nl$/icons/full/eview16/projects.gif"
    category="org.eclipse.jdt.ui.java.browsing"
    class="org.eclipse.jdt.internal.ui.browsing.ProjectsView"
    id="org.eclipse.jdt.ui.ProjectsView" /> 
  <view name="%Browsing.packagesViewName"
  ...
    id="org.eclipse.jdt.ui.PackagesView" /> 
  <view name="%Browsing.typesViewName" 
    ...
    id="org.eclipse.jdt.ui.TypesView" /> 
  <view name="%Browsing.membersViewName" 
    ...
    id="org.eclipse.jdt.ui.MembersView" /> 
</extension> 

Теперь можно вернуться к меню для представлений. Главной особенностью при реализации точки расширения viewActions является реализация интерфейса org.eclipse.ui.IViewActionDelegate:

public interface IViewActionDelegate extends IActionDelegate 
{
  public void init(IViewPart view);
}

Метод init() обычно используется для сохранения передаваемого ему значения «рабочего» представления:

public class AddToFavoritesActionDelegate implements 
  IObjectActionDelegate, IViewActionDelegate 
{

  private IViewPart activeView;

  public void init(IViewPart view) 
  {
    this.activeView = view;
  }
   ...
}

Для задания кнопок на панели инструментов представления и выпадающего меню точки расширения определены элементы <action> и <menu> – подобно тому, как это делалось для точки расширения actionSets.

При создании расширения для редактора, а не представления, нужно использовать интерфейс IEditorActionDelegate:

public interface IEditorActionDelegate extends IActionDelegate 
{
  public void setActiveEditor(IAction action, IEditorPart targetEditor);
}

Класс, реализующий этот интерфейс, похож на класс, реализующий интерфейс IObjectActionDelegation.

Основным элементом при создании расширения для точки editorActions является <editorContribution>. Элементы <action> и <menu> при работе с редактором – почти те же, что и при работе с представлением.

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

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

Для создания перспектив предусмотрена точка расширения org.eclipse.ui.perspectives. Структура ее описания в plugin.xml очень проста:

<extension point="org.eclipse.ui.perspectives">
  <perspective
    class="my.demo.MyPerspectiveFactory"
    id="my.demo.my_perspective"
    name="name">
    <description>
      Это мое описание перспективы
    </description>
  </perspective>
</extension>

Здесь мы впервые столкнулись с довольно обычной ситуацией, когда класс расширения, объявленный в дескрипторе плагина, является не реализацией готового объекта, а, по сути, фабрикой нужных объектов. Класс MyPerspectiveFactory должен реализовывать интерфейс org.eclipse.ui.IPerspectiveFactory:

public interface IPerspectiveFactory 
{
  public void createInitialLayout(IPageLayout layout);
}

Перспектива Eclipse, по сути, представляет собой контейнер для «дочерних» элементов интерфейса – представлений, редакторов, панелей, меню. Ее задача (с точки зрения создания визуального представления) – задать начальную компоновку (Layout) для управления расположением панелей, задать начальное состояние элементов управления и т.д. После того, как перспектива создана и получила начальную компоновку, созданный экземпляр фабрики перспектив больше не нужен.

Для задания начального состояния перспективы программист реализует метод createInitialLayout(). Eclipse включает в себя эксперт, который генерирует «демонстрационную» перспективу как образец для создания перспективы собственной. Код метода createInitialLayout() для компоновки стандартных представлений может выглядеть примерно так:

public void createInitialLayout(IPageLayout layout) 
{
  // Получаем область редактора.
  String editorArea = layout.getEditorArea();

  // Вверху слева: место для представлений Resource Navigator и Bookmarks
  IFolderLayout topLeft =
    layout.createFolder("topLeft", IPageLayout.LEFT, 0.25f, editorArea);
  topLeft.addView(IPageLayout.ID_RES_NAV);
  topLeft.addPlaceholder(IPageLayout.ID_BOOKMARKS);

  // Внизу слева: представления Outline и Property Sheet
  IFolderLayout bottomLeft = 
    layout.createFolder("bottomLeft", IPageLayout.BOTTOM, 0.50f,"topLeft");
  bottomLeft.addView(IPageLayout.ID_OUTLINE);
  bottomLeft.addView(IPageLayout.ID_PROP_SHEET);

  // Внизу справа: Task List
  layout.addView(
    IPageLayout.ID_TASK_LIST, IPageLayout.BOTTOM, 0.66f, editorArea);
}

Если нужно не создавать перспективу заново, а «статически», на уровне дескрипторов плагина, изменить существующую, нужно использовать точку расширения org.eclipse.ui.perspectiveExtensions. По сути, создание расширения для этой точки заключается в формировании перспективы на уровне заданий XML-описаний.

Создание представления, напротив, обычно выполняется в виде написания производного класса на базе стандартного класса org.eclipse.ui.part.ViewPart (программист может просто реализовать интерфейс org.eclipse.ui.IViewPart, но, как правило, проще наследовать готовый класс).

Структура XML-описания нужной точки расширения (org.eclipse.ui.views) содержит два основных элемента – собственно <view> и элемент <category>, задающий группу, к которой относится данное представление. Категории введены для удобства работы с представлениями. Категории представлений видны, например, при выборе отображаемых представлений (меню Window->Show View->Other...):


Рисунок 2.

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

Элементы <view> и <category> находятся на одном уровне, и привязка представления к конкретной категории выполняется за счет задания идентификаторов:

<extension point="org.eclipse.ui.views">
  <category
    name = "My Category"
    id = "my.demo.my_category_id ">
  </category>
  <view
    name = "My View"
    icon = "icons/sample.gif"
    category = "my.demo.my_category_id"
    class = "my.demo.MyViewClass"
    id = "my.demo.my_view_class_id">
  </view>
</extension>

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

Другие часто используемые точки расширений

Здесь очень кратко будут рассмотрены (точнее сказать – упомянуты) те точки расширения, на которые стоит обратить внимание в первую очередь.

Точка для создания приложений.

Платформа Eclipse позволяет создавать приложения (как плагины), которые могут быть запущены под управлением платформы. Точка расширения – org.eclipse.core.runtime.application . Основные параметры – класс приложения (с методом main()), который реализует интерфейс org.eclipse.equinox.application.IApplication (используемый ранее для этой же цели интерфейс org.eclipse.core.runtime.IPlatformRunnable объявлен устаревшим). Основные элементы дескриптора – полное имя главного класса приложений, параметры, передаваемые этому приложению (в виде пар «имя-значение»), и идентификатор приложения, который указывается в качестве значения ключа -application при запуске платформы.

Точки для создания команд и привязки их действиям

В ранних версиях Eclipse клавиатурные акселераторы вызовов действий были частью описания действий. Теперь команды задаются отдельно, действия – отдельно, привязка команд к действиям – отдельно. Это позволяет избежать ошибок и повысить гибкость средств настройки.

Точки расширения – org.eclipse.ui.commands (задание команд, по категориям – примерно так же, как это сделано с представлениями) и org.eclipse.ui.bindings (задание сочетаний клавиш и привязка к команде по ее идентификатору). Действие (action) сопоставляется с командой (по ее идентификатору) с помощью указания идентификатора команды в атрибуте definitionId действия.

Заключение

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


Эта статья опубликована в журнале RSDN Magazine #1-2008. Информацию о журнале можно найти здесь
    Сообщений 0    Оценка 16 [+0/-1]         Оценить