Сообщений 12    Оценка 270        Оценить  
Система Orphus

Проект Eclipse

Автор: Askar Rahimberdiev
Borland

Источник: RSDN Magazine #4-2004
Опубликовано: 22.01.2005
Исправлено: 13.03.2005
Версия текста: 1.0
Введение
История
Архитектура
Platform
Workspace
Workbench
JDT
Инкрементальная компиляция
Экстремальное программирование и Eclipse
PDE
Плагины
Расширения
Пример: создание инкрементального билдера
Разработчикам приложений

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

Введение

Что такое Eclipse? Ответ, который первым приходит в голову – “это еще одна IDE для Java”. Однако Eclipse – нечто большее, чем просто IDE, и к тому не ограничен рамками Java. Сами создатели Eclipse называют его платформой для разработки интегрированных приложений. Какой смысл они вкладывают в это понятие?

В настоящее время разработка Eclipse ведется в рамках нескольких проектов. Кратко рассмотрим основные из них – Eclipse и Eclipse Tools. Читатели могут получить полную информацию о проектах, исходные коды и собранные дистрибутивы на сайте http://eclipse.org.

Проект Eclipse включает в себя платформу для разработки приложений, IDE для Java, построенную на ее основе а также средства, необходимые разработчику приложений. Дистрибутив, объединяющий эти компоненты вместе с исходными кодами и пользовательской документацией, называются Eclipse SDK – именно с него рекомендуется начинать знакомство с Eclipse. Eclipse SDK содержит все необходимое для разрабочиков Java: инкрементальный компилятор, редактор Java с подсветкой синтаксиса, контекстным автозавершением и поддержкой шаблонов, отладчик, поддержку автоматического рефакторинга и навигации по исходному коду. Для работы Eclipse SDK необходима JRE (Java runtime environment) версии не ниже 1.4.1 и операционная система Windows 98/ME/2000/XP, Linux, Solaris, AIX, HP-UX или Mac OSX.

В проекте Eclipse Tools собраны приложения, созданные на базе Eclipse, такие как CDT (IDE для С и C++) и EMF (поддержка генерации исходного кода Java для приложений моделирования). Именно тот факт, что все приложения Eclipse используют общие концепции, интерфейсы и технические решения, позволяет говорить об их упомянутой выше интеграции.

История

Разработка Eclipse началась в апреле 1999 года совместными усилиями компаний OTI и IBM. В октябре 2001 года вышел релиз 1.0, а месяцем позже IBM полностью открыла исходный код Eclipse. Тогда же был образован совет управляющих (Board of Stewards) Eclipse, призванный обеспечить развитие проекта и создать вокруг него сообщество разработчиков. К концу 2002 года в совет входило около тридцати компаний-участников

Тем не менее, поддержку Eclipse со стороны крупных компаний-разработчиков ПО существенно ограничивал тот факт, что весь проект и, самое главное, перспективы его развития, прочно ассоциировались с одной компанией – IBM. Действительно, вклад IBM в создание Eclipse был настолько велик, что весь проект неизбежно воспринимался как ее собственная разработка. Однако главной задачей самой IBM, сделавшей ставку на технологию Java (а не .NET), было добиться широкого распространения качественных продуктов для разработчиков на этой платформе. Чтобы завоевать доверие крупных поставщиков ПО, в начале 2004 года Eclipse был преобразован в некоммерческую ассоциацию, гарантирующую, что все технологии и исходный код Eclipse останутся открытыми и бесплатными.

Архитектура

Eclipse построен в виде набора расширяемых подсистем, а не как единое монолитное приложение. В Eclipse входят три подпроекта, разрабатываемых более или менее независимо друг от друга – Platform, JDT (Java development tools) и PDE (Plug-in development environment). Platform предоставляет базовые сервисы, JDT позволяет разрабатывать приложения Java, а PDE – новые компоненты Eclipse. Далее в статье анализируются основные свойства и внутреннее устройство каждого из подпроектов.


Рисунок 1 Архитектура Eclipse.

Platform

Platform является ядром Eclipse. Сама по себе эта подсистема не содержит особенно полезной для пользователей функциональности, но без нее невозможна работа остальных подпроектов Eclipse. Сервисы, которые обеспечивает Platform, позволяют разработчикам определять видимые пользователю артефакты, создавать пользовательские интерфейсы, работать с системами контроля версий, средами отладки и справочной системой. Соответствующими компонентами Platform, реализующими эти сервисы, являются Workspace (управление контентом), Workbench (базовый пользовательский интерфейс Eclipse), Team, Debug и Help.

Workspace

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

Ресурсы

Базовые понятия, определяемые Workspace – это рабочая область (workspace), проект (project), папка (folder) и файл (file). В терминологии Eclipse все эти объекты называются ресурсами (resources).

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

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

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

Проекты могут зависеть друг от друга – это значит, что проект A может ссылаться на проект B и использовать ресурсы из него.

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

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

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

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

Маркеры

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

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

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

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

Проекты

Каким образом Eclipse будет интерпретировать тот или иной проект, целиком зависит от его свойств. Наиболее важными из поддерживаемых Platform свойств являются типы проектов (project natures) и билдеры (builders).

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

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

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

Workbench

Перспективы, виды, редакторы

Компонент Workbench определяет базовый пользовательский интерфейс (UI) Eclipse, а также предоставляет другим компонентам средства для создания своих собственных интерфейсов. Основные объекты интерфейса, с которыми работает Workbench – это редакторы (editors), виды (views) и перспективы (perspectives).


Рисунок 2 Workbench.

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

Назначение редакторов заключается в предоставлении возможности просмотра и редактирования файлов. Визуальное представление содержимого файла зависит от его типа и может меняться от традиционного текстового редактора с подсветкой синтаксиса для сценариев Ant до графического редактора для изображений в формате gif.

Виды, в отличие от редакторов, не обязательно связаны с каким-либо конкретным ресурсом. Они могут быть использованы для отображения содержимого активного в данный момент редактора (наподобие вида Outline), элемента, выбранного в другом виде (история версий ресурса в CVS), а также данных, полученных любым другим путем (таких, как отчет о выполнении тестов JUnit).

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

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

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

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

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

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

Графический интерфейс

Фундаментом, на котором построены все графические средства Eclipse, является входящий в Workbench компонент SWT (Standard widget toolkit). SWT является основным средством создания пользовательского интерфейса для приложений на основе Eclipse. Поскольку для прорисовки элементов интерфейса SWT максимально использует средства оконного менеджера операционной системы (Win32, GTK, Motif), приложения, построенные на базе SWT, визуально не отличаются от “родных” приложений, разработанных специально для конкретной операционной системы. При этом они сохраняют совместимость со всеми платформами, поддерживаемыми Eclipse.

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

SWT (в виде набора библиотек) может быть использован в качестве замены Swing и AWT, отдельно от Eclipse и компонента Workbench.

До последнего времени основным недостатком SWT было отсутствие визуального редактора для создания графических интерфейсов на его основе. Безусловно, разработку кода графического приложения с нуля трудно назвать увлекательной или интеллектуальной задачей. Заполнить этот пробел призван недавно стартовавший проект Eclipse Visual Editor.

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

JDT

JDT – второй из основных подпроектов, составляющих Eclipse SDK. Это и есть упомянутая “IDE для Java”, содержащая инкрементальный компилятор Java, редакторы, средства для отладки, тестирования, навигации и рефакторинга исходного кода.

Важно понимать, что JDT построен исключительно на основе подпроекта Platform и не использует каких-либо закрытых или недокументированных интерфейсов. Это значит, что при наличии желания и ресурсов можно разработать компонент, функционально аналогичный JDT, для любого другого языка – от С++ до Python, и тем самым превратить Eclipse в, например, “IDE для Python”.

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


Рисунок 3 Перспектива Java.

Инкрементальная компиляция

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

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

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

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

Экстремальное программирование и Eclipse

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

Еще одним приятным свойством JDT, упрощающим применение рекомендаций методики экстремального программирования или Test driven development (TDD), является возможность запуска тестов JUnit прямо из IDE. Какой бы незначительной ни казалась эта деталь, она экономит существенное количество времени в процессе разработки. При работе в традиционной IDE цикл кодирование-модульное тестирование (unit testing) выглядит следующим образом: кодирование, сборка проекта, запуск JUnit, запуск тестов и анализ результатов. В JDT проект всегда находится в откомпилированном виде, поэтому этапы сборки и запуска JUnit пропадают, а выполнение набора тестов становится быстрее и проще. Этого оказывается достаточно для того, чтобы подход с написанием модульных тестов перед собственно кодом приложения начал выглядел намного более привлекательно.

PDE

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

Плагины

Как было отмечено выше, Eclipse предоставляет широкие возможности для расширения. В основе архитектуры, делающей это возможным, лежит использование плагинов (plug-in). Вместо того чтобы находиться в одном монолитном jar-файле, код Eclipse разделен на множество модулей, загружаемых динамически и независимо друг от друга (хотя каждый плагин может указать список плагинов, которые должны быть загружены до него). Для того, чтобы сократить время запуска платформы, плагины загружаются только перед непосредственным использованием (разумеется, полный список установленных плагинов доступен всегда).

Для того, чтобы Eclipse загрузил ваш плагин, необходимо написать дескриптор плагина – файл в формате XML под названием plugin.xml, который можно создать как при помощи PDE, так и в любом текстовом редакторе. Дескриптор сообщает Eclipse, какие ресурсы входят в плагин и каким образом платформа может их использовать. В ресурсы обычно входят библиотеки jar, текстовые сообщения в файлах .properties, графические изображения и т.п.

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

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

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

Расширения

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

Каким образом разработчики могут добавлять в Eclipse, например, свои редакторы? Eclipse, а точнее входящий в компонент Workbench плагин org.eclipse.ui, определяет точку расширения для редакторов. (Как мы помним, именно Workbench определяет пользовательский интерфейс Eclipse.) Основная задача точки расширения – объявить уникальный идентификатор, который будет использован конкретными расширениями для ссылки на точку расширения. Вот как выглядит соответствующий фрагмент дескриптора плагина org.eclipse.ui:

<extension-point
  id="editors"
  name="%ExtPoint.editors"
  schema="schema/editors.exsd" />

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

Следующий пример определяет стандартный текстовый редактор Eclipse (обратите внимание на то, что задано расширение файлов, с которыми работает этот редактор - txt).

<extension point="org.eclipse.ui.editors">
  <editor name="%Editors.DefaultTextEditor"
    extensions="txt" 
    icon="icons/full/obj16/file_obj.gif"
    class="org.eclipse.ui.editors.text.TextEditor"
    contributorClass="org.eclipse.ui.editors.text.TextEditorActionContributor"
    id="org.eclipse.ui.DefaultTextEditor" />
</extension>

Как происходит открытие файла для редактирования? Workbench ищет в списке всех расширений org.eclipse.ui.editors те редакторы, которые способны открыть файл с данным расширением (задается атрибутом extensions). Если обнаружено более одного подходящего редактора, пользователь может выбрать, какой из доступных редакторов он хочет использовать в данном случае. Существует соглашение о том, что все редакторы Eclipse обязаны реализовать интерфейс org.eclipse.ui.IEditorPart, поэтому Workbench создает объект класса, указанного в атрибуте class расширения, и приводит его к известному ему IEditorPart. В дальнейшем управление жизненным циклом редактора осуществляется только через этот интерфейс.

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

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

Типичное функционирование механизма расширений пояснит рисунок 4.


Рисунок 4. Механизм расширения Eclipse.

Клиенты точки расширения P (они не обязательно должны находится в плагине A, объявляющем точку расширения) могут получить список обнаруженных расширений P (в этом примере – единственное расширение в плагине B), создать их экземпляры и работать с ними через общий интерфейс I, объявленный в том же плагине, что и точка расширения.

Из приведенного примера может показаться, что существует жесткая взаимосвязь “один плагин – одна точка расширения”. На самом деле никакого ограничения на количество расширений, с которыми может работать один плагин, нет. Один и тот же плагин может объявлять свои точки расширения и подключаться к сторонним – критерием объединения расширений в одном плагине является лишь логическая целесообразность.

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

При помощи описанного механизма расширений может быть расширена практически вся функциональность Eclipse – перспективы, редакторы, виды, страницы настроек, инкрементальные билдеры. Предположим, что перед вами стоит задача создания инструментария для поддержки Web-сайта. Вы можете создать новый вид проекта “Web project”, редактор html, средства синхронизации с web-серверами, и разместить все новые элементы интерфейса в отдельной перспективе.

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

Пример: создание инкрементального билдера

Эффективность работы в Eclipse велика не только благодаря таким возможностям, как, например, поддержка рефакторинга. Множество, казалось бы, незначительных деталей также значительно ускоряет процесс разработки. Одна из них – вид Tasks, в котором отображается список задач (tasks). Задача – это напоминание о некоторой деятельности, после выполнения которой задача считается выполненной. JDT автоматически создает задачи для комментариев TODO и FIXME, найденных в исходном коде. Эти задачи позволяют быстро просмотреть фрагменты кода, нуждающиеся в дополнениях или исправлениях. После редактирования кода разработчик удаляет комментарий TODO или FIXME, и соответствующая задача исчезает из вида Tasks.

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

Разумеется, для поддержки локализации все текстовые сообщения вынесены в файлы .properties, а не закодированы в исходных файлах Java. Поэтому задачи для непереведенных сообщений не появятся в виде Tasks автоматически, поскольку JDT создает задачи TODO только для исходных файлов java. Чтобы получить новую функциональность, нам придется создавать расширение Eclipse.

Прежде всего, при помощи стандартного мастера New Plug-in Project, создадим новый плагин под названием com.mycompany.propbuilder. Вот как выглядит дескриптор плагина plugin.xml:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin
  id="com.mycompany.propbuilder"
  name="Property Builder Plug-in"
  version="1.0.0"
  provider-name="MYCOMPANY"
  class=”com.mycompany.propbuilder.PropertyBuilderPlugin”>

  <runtime>
    <library name="propbuilder.jar">
      <export name="*"/>
    </library>
  </runtime>

  <requires>
    <import plugin="org.eclipse.core.runtime"/>
    <import plugin="org.eclipse.core.resources"/>
  </requires>
</plugin>

Мы сразу добавили ссылку на плагин org.eclipse.core.resources, поскольку он понадобится в дальнейшем. Кроме того, в мастере создания проекта была выбрана опция создания класса Java для плагина – PropertyBuilderPlugin. Сразу же добавим в этот класс статический метод для сообщения об ошибках, которые могут произойти во время работы плагина. Сообщения будут записаны в лог-файл eclipse/workspace/.metadata/.log.

СОВЕТ

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

public class PropBuilderPlugin extends Plugin 
{
  public static void log(Exception e) 
  {
    getDefault().getLog().log(
      new Status(
        IStatus.ERROR, 
        getDefault().getBundle().getSymbolicName(), 
        0, 
        e.getMessage(), 
        e)
    );
  }
}

Каким образом можно добавить свои задачи в вид Tasks? С точки зрения реализации, каждая задача представлена отдельным маркером. Вид Tasks отображает маркеры, соответствующие задачам, выделяя их из общего списка маркеров. Для того, чтобы маркеры можно было разделять на группы (например, отличать сообщения компилятора от задач), каждый маркер обладает своим типом (не надо путать этот тип с классами Java). Тип маркеров задач объявлен в плагине org.eclipse.core.resources и имеет идентификатор org.eclipse.core.resources.taskmarker.

Итак, для каждого непереведенного сообщения нам необходимо создавать новый экземпляр маркера. Чтобы легко отделять наши маркеры от задач, создаваемых JDT, их тип должен отличаться от org.eclipse.resources.taskmarker, но в то же время он должен указывать на то, что наши маркеры являются маркерами задач. Этого можно добиться при помощи механизма наследования – тип маркера может быть выведен из одного или нескольких базовых типов. В нашем случае, таким базовым типом является taskmarker; при этом все атрибуты, определенные в базовом типе, наследуются его потомками. Вот как выглядит объявление нового типа маркеров в файле plugin.xml:

<extension
    point="org.eclipse.core.resources.markers"
    id="propmarker"
    name="%PropMarker.name">
  <super type="org.eclipse.core.resources.taskmarker" />
  <persistent value="true" />
</extension>

Единственный атрибут, который не передается при наследовании маркеров – сохраняемость. Поскольку мы не хотим, чтобы созданные задачи пропадали при перезапуске Eclipse, атрибут persistent устанавливается явным образом.

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

Такой механизм обработки изменений в ресурсах проекта уже реализован в Eclipse – это не что иное, как инкрементальные билдеры. Нам остается только создать класс Java для своего билдера (назовем его PropertyBuilder) и поместить его объявление в дескриптор плагина.

<extension
    id="builder"
    name="%PropBuilder"
    point="org.eclipse.core.resources.builders">
  <builder >
    <run class="com.mycompany.propbuilder.PropertyBuilder" />
  </builder>
</extension>

Теперь, когда определены основные интерфейсы и алгоритмы, остается только написать класс PropertyBuilder. Как и любой инкрементальный билдер, он должен быть выведен из класса org.eclipse.resources.InrementalProjectBuilder. Главными методами билдера являются build() и clean() – они вызываются Eclipse для обработки проекта и удаления результатов работы соответственно.

package com.mycompany.propbuilder;

import java.io.*;
import java.util.*;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;

public class PropertyBuilder extends IncrementalProjectBuilder 
{
  private static final String MARKER_ID =
    "com.mycompany.propbuilder.propmarker"; //$NON-NLS-1$

  protected void clean(IProgressMonitor monitor) throws CoreException 
  {
    // удалить все маркеры, созданные для файлов в проекте
    List removed = new LinkedList();
    removed.add(getProject());
    removeMarkers(removed);
  }

  protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
    throws CoreException 
  {
    // список файлов, в которых необходимо искать сообщения TODO
    List added = new LinkedList();
    // все маркеры для этих файлов необходимо удалить
    List removed = new LinkedList();
    IResourceDelta delta = getDelta(getProject());
    if (kind == FULL_BUILD 
      || (delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) 
    {
      // начинаем обработку всех файлов в проекте
      buildAll(added, removed);
    }
    else
    {
      // начинаем обработку файлов, указанных в delta
      buildDelta(delta, added, removed);
    }
    // удалить устаревшие маркеры
    removeMarkers(removed);
    // создать новые маркеры
    processFiles(monitor, added);
    return null;
  }

  private void buildAll(final List added, final List removed) 
  {
    IProject project = getProject();
    // удалить все маркеры, созданные для файлов в проекте
    removed.add(project);
    try 
    {
      // посетить все ресурсы, входящие в проект
      project.accept(
        new IResourceVisitor() 
        {
          public boolean visit(IResource res) throws CoreException 
          {
            // мы ищем файлы с расширением .properties 
            if (res instanceof IFile &&
              "properties".equals(res.getFileExtension())) //$NON-NLS-1$
            {
              added.add(res);
            }
            // продолжить обход дочерних ресурсов текущего ресурса
            return true;
          }
        }
      );
    }
    catch (CoreException e)
    {
      PropBuilderPlugin.log(e);
    }
  }

  private void buildDelta(IResourceDelta delta, final List added, 
    final List removed)
  {
    IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() 
    {
      public boolean visit(IResourceDelta delta) 
      {
        if ((delta.getFlags() & IResourceDelta.CONTENT) == 0) 
        {
          // содержимое данного ресурса не менялось
          return true;
        }
        
        IResource resource = delta.getResource();
        // мы ищем файлы с расширением .properties
        if (resource.getType() == IResource.FILE &&
          "properties".equals(resource.getFileExtension())) //$NON-NLS-1$
        {
          switch (delta.getKind()) 
          {
            case IResourceDelta.ADDED:   added.add(resource);   break;
            case IResourceDelta.REMOVED: removed.add(resource); break;
            case IResourceDelta.CHANGED:
              removed.add(resource);
              added.add(resource);
              break;
          }
        }
        // продолжить обход дочерних ресурсов текущего ресурса
        return true;
      }
    };
    try {
      // посетить все измененные ресурсы 
      delta.accept(visitor);
    } 
    catch (CoreException e) 
    {
      PropBuilderPlugin.log(e); 
    }
  }

  void removeMarkers(final List resources) 
  {
    IWorkspaceRunnable operation = new IWorkspaceRunnable() 
    {
      public void run(IProgressMonitor monitor) throws CoreException 
      {
        Iterator i = resources.iterator();
        while (i.hasNext()) 
        {
          IResource r = (IResource) i.next();
          // удалить в данном ресурсе все маркеры типа propmarker
          r.deleteMarkers(
            MARKER_ID,
            true,
            IResource.DEPTH_INFINITE);
        }
      }
    };
    try 
    {
      // выполнить удаление маркеров как атомарную операцию
      ResourcesPlugin.getWorkspace().run(operation, null);
    } 
    catch (CoreException e) 
    {
      PropBuilderPlugin.log(e);
    }
  }

  private void processFiles(IProgressMonitor monitor, final List files) 
  {
    IWorkspaceRunnable operation = new IWorkspaceRunnable() 
    {
      public void run(IProgressMonitor monitor) throws CoreException 
      {
        // всего предстоит обработать files.size() файлов
        monitor.beginTask("Analyzing properties", files.size()); //$NON-NLS-1$
        Iterator i = files.iterator();
        while (i.hasNext()) 
        {
          readTodos((IFile) i.next());
          // обновить индикатор состояния 
          monitor.worked(1);
        }
        // обработка завершена
        monitor.done();
      }

      // найти все строки, содержащие TODO в данном файле .properties
      private void readTodos(IFile file) throws CoreException 
      {
        BufferedReader in = new BufferedReader(
          new InputStreamReader(file.getContents()));
        int line = 1;
        String str;
        try {
          while ((str = in.readLine()) != null) 
          {
            if (str.indexOf("TODO") >= 0) { //$NON-NLS-1$
              reportTask(file, line, str);
            }
            line++;
          }
        } 
        catch (IOException e) 
        {
          PropBuilderPlugin.log(e);
        }
      }

      private void reportTask(IResource resource, int line, String message)
        throws CoreException 
      {
        // создать маркер типа propmarker для данного файла
        IMarker marker = resource.createMarker(MARKER_ID);
        // установить номер строки
        marker.setAttribute(IMarker.LINE_NUMBER, line);
        // используем сам текст строки в качестве наименования задачи
        marker.setAttribute(IMarker.MESSAGE, message);
        // запретить пользователю редактировать и удалять задачу
        marker.setAttribute(IMarker.USER_EDITABLE, false);
      }
    };

    try 
    {
      // выполнить поиск непереведенных сообщений как атомарную операцию
      ResourcesPlugin.getWorkspace().run(operation, monitor);
    } 
    catch (CoreException e) 
    {
      PropBuilderPlugin.log(e);
    }
  }
}
СОВЕТ

После каждого создания, изменения или удаления маркера Workspace рассылает всем зарегистрированным слушателям сообщение об изменении ресурса. Из-за этого создание нескольких сотен или тысяч маркеров может оказаться весьма продолжительной операцией. В таких случаях их следует создавать в контексте метода IWorkspace.run(), вызов которого считается атомарной операцией. Соответственно, независимо от количества созданных маркеров, слушателям будет разослано только одно сообщение.

Как пользоваться новым расширением Eclipse? Прежде всего, необходимо установить плагин com.mycompany.propbuilder. Для этого нужно скопировать файлы plugin.xml и propbuilder.jar в директорию eclipse/plugins/com.mycompany.propbuilder и перезапустить Eclipse. После этого следует выбрать проекты, в которых мы собираемся искать непереведенные сообщения. В случае реального приложения такая возможность должна быть доступна пользователю через графический интерфейс, однако для упрощения примера мы просто добавим ссылку на билдер PropertyBuilder в файлах .project соответствующих проектов:

<buildSpec>
  ...
  <buildCommand>
    <name>com.mycompany.propbuilder.builder</name>
      <arguments>
      </arguments>
  </buildCommand>
</buildSpec>

Наконец, включим отображение нового типа задач в виде Tasks:


Рисунок 5 Настройка вида Tasks.

Теперь в Tasks должны появиться созданные плагином com.mycompany.propbuilder задачи.


Рисунок 6 Новые задачи в виде Tasks.

Разработчикам приложений

Рассмотрим два аспекта создания приложений на базе Eclipse – правовой и технический.

Юридическую сторону вопроса призваны решить лицензии, под которыми распространяется Eclipse SDK – CPL (Common Public License) и более поздняя EPL (Eclipse Public License). Они позволяют использовать, распространять и продавать лицензированные исходные и бинарные коды без уплаты вознаграждения правообладателям. Этим уже воспользовались для создания коммерческих продуктов на базе Eclipse такие поставщики ПО, как Borland, IBM, SAP.

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

Основной целью проекта Eclipse является интеграция приложений для разработки в рамках различных IDE (одной из этих IDE является сам Eclipse SDK, а именно Platform, дополненный JDT и PDE). В то же время, Eclipse содержит множество компонентов, которые могут быть использованы приложениями самого широкого спектра, не нуждающимися, вообще говоря, в полной инфраструктуре Eclipse (примерами таких компонентов могут служить SWT и JFace). Для поддержки подобных приложений было введено понятие Rich Client Platform (RCP). RCP – это базовый набор компонентов Eclipse, который позволяет использующим его приложениям полностью контролировать внешний вид создаваемого пользовательского интерфейса (перспективы, меню, панели инструментов и т.п.).

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

На прилагающемся к журналу компакт-диске находится Eclipse SDK 3.0.1.


Эта статья опубликована в журнале RSDN Magazine #4-2004. Информацию о журнале можно найти здесь
    Сообщений 12    Оценка 270        Оценить