вторник, 17 июня 2008 г.

TabbedPropertySheet vs PropertySheet

В Eclipse в настоящее время реализованы два механизма отображения свойств в PropertyView:
  • PropertySheet - "обычное" представление в виде двухколоночной таблицы "имя параметра" / редактор значения. Поддержка реализуется через наследования от IPropertySource (или с помощью адаптирования). Очень просто реализуется для простых свойств. Подробнее можно почитать здесь
  • TabbedPropertySheet - представление в виде закладок на которых размещаются созданные "вручную" секции с редакторами свойств. Реализуется относительно сложнее, зато результат более адаптирован задачам, то есть более подходит в случае реализации редакторов "сложных" свойств. Подробнее можно почитать здесь.
Однако основной вопрос для меня был: "как можно выбрать какую реализацию будет применять Eclipse для отображения свойств моего view". Оказалось всё просто. Необходимо перегрузить метод getAdapter для вашего view с тем , чтобы он адаптировал ваше view к IPropertySheet:

для PropertySheetPage

public Object getAdapter(Class adapter) {
if (adapter == IPropertySheetPage.class)
return new PropertySheetPage();
return super.getAdapter(adapter);
}

для TabbedPropertySheet
 
public Object getAdapter(Class adapter) {
if(adapter.equals(IPropertySheetPage.class))
return new TabbedPropertySheetPage(new ITabbedPropertySheetPageContributor(){
@Override
public String getContributorId() {
return "MyContributorID";
}
});
return super.getAdapter(adapter);
}


Все это также применимо к CommonNavigator. Для меня была не совсем очевидной сама возможность наследования от CommonNavigator. Но эксперимент показал, что всё нормально :)
Единственное надо не забыть указать именно свой класс-потомок CommonNavigator в объявлении расширения "org.eclipse.ui.views".

К сожалению не удалось обойтись без наследования. Так как определение фабрики/адаптера CommonNavigator к IProperySheePage не работает. Видимо из какого-то модуля аналогичный адаптер уже регистрируется.

четверг, 12 июня 2008 г.

Использование внешних библиотек plugin'ах Eclipse

Для того, чтобы использовать в plugin'ах классы из внешних библиотек их (jar'ы) придётся включать (физически) внутрь нашего plugin'а.
Разместив jar внутри какой-либо папки проекта plugin'а необходимо в редакторе манифеста на вкладке Runtime в блоке classpath добавить этот самый файл библиотеки.



Для того, чтобы не включать одни и те-же библиотеки в каждый plugin, можно их обернуть в специальный plugin, который в свою очередь экспортирует нужные пакеты для других plugin'ов. Для этого на той-же вкладке Runtime нужно добавить необходимые пакеты в поле Exported pakages




Необходимо отметить, что экспорт пакетов будет доступен только если plugin был сделан как java проект.

Bug при запуске Eclipse 3.4.0

При работе в Eclupse 3.4.0 (Ganimede RC4) обнаружился такой баг - попытка запустить среду на отладку (с целью отладить plug'in) запуск останавливается с ошибкой:

org.eclipse.swt.SWTError: XPCOM error -2147467262
at org.eclipse.swt.browser.Mozilla.error(Mozilla.java:1597)
at org.eclipse.swt.browser.Mozilla.setText(Mozilla.java:1820)
at org.eclipse.swt.browser.Browser.setText(Browser.java:733)
...

исследование вопроса привело сюда. Тут же указан обходной рецепт. Для меня он выглядел как указание в конфигурации отладки параметра:
-Dorg.eclipse.swt.browser.XULRunnerPath=/dev/null.

Что и помогло в итоге. Может и Вам поможет.

понедельник, 5 мая 2008 г.

Адаптеры Eclipse

Перевод статьи:
ECLIPSE ADAPTERS
A HANDS-ON, HAND-HOLDING EXPLANATION

Copyright © 2007 Jeffrey Ricker LLC
November 1, 2007
http://www.jeffreyricker.com

Из яблок в апельсины.


Когда вы создаёте plug-in'ы Eclipse - вы быстро сталкиваетесь лицом к лицу с адаптерами. Если вы не достаточно хорошо понимаете шаблон adapter, то адаптеры могут сбить вас с толку. Адаптеры Eclipse на самом деле очень просты. Я постараюсь сделать их ещё проще с помощью данной статьи.

Адаптеры работают по простой схеме: Получив некоторый адаптируемый объект А, дайте мне соотвествующий ему объект типа Б. Интерфейс адаптера Eclipse показан на листинге 1. Интерфейс возвращает объект который является экземляром указанного класса, который (экземпляр) ассоциирован с объектом или null, если такой ассоциированный объект не существует.

Листинг 1 интерфейс адаптера в Eclipse

package org.eclipse.core.runtime;
public interface IAdaptable {
public Object getAdapter(Class adapter);
}


Если я хочу перейти от яблок к апельсинам, то я должен сделать что-то похожее на Листинг 2. В данном случае IApple расширяет интерфейс IAdaptable.

Листинг 2 Адаптирование от яблок к апельсинам

IApple macintosh = new Macintosh();
IOrange orange = (IOrange) macintosh.getAdapter(IOrange.class);
if (orange==null)
log("No orange");
else
log("Created a "+ orange.getClass().getCanonicalName());


Одним из первичных применений адаптеров является разделение кода модели откода представлений (views) в шаблонах view-model-controller или view-model-presenter. Мы не хотим помещать информацию отображения (такую как иконки) в нашу модель яблока. Мы создаём отдельный класс для отображения яблока. Мы сделаем это с адаптерами как показано на Листенге 3.

Листинг 3. Использование адаптеров для отделения модели от представления

IApple apple = new Macintosh();
ILableProvider label = (ILabelProvider)apple.getAdapter(ILabelProvider.class);
String text = label.getText(apple);


Адаптеры позволяют нам преобразовывать объекты к другим видам (целям), о которые эти объекты могут ничего не знать. Например, объекты "яблоки" в Листинге 3 не должны знать что-либо о постащике ярлыков (label provider). Мы можем реализовать метод getAdapter() вручную для каждого объекта яблока, но это противоречит нашим целям. Вместо этого мы должны переложить адаптацию на платформу, как показано на Листинге 4.

Листинг 4 Адаптирование с помощью платформы

public abstract class Fruit implements IAdaptable{
public Object getAdapter(Class adapter){
return Platform.getAdapterManager().getAdapter(this, adapter);
}
}

Фабрики адаптеров


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

Листинг 5. Фабрика адаптеров яблок в апельсины

package com.jeffricker.fruit;
import org.eclipse.core.runtime.IAdapterFactory;
import com.jeffricker.fruit.apples.IApple;
import com.jeffricker.fruit.apples.Macintosh;
import com.jeffricker.fruit.oranges.IOrange;
import com.jeffricker.fruit.oranges.Mandarin;
/**
* Converts apples to oranges
* @author Ricker
*/
public class OrangeAdapterFactory implements IAdapterFactory {
public Object getAdapter(Object adaptableObject, Class adapterType) {
if (adapterType == IOrange.class) {
if (adaptableObject instanceof Macintosh) {
return new Mandarin();
}
}
return null;
}
public Class[] getAdapterList() {
return new Class[]{ IOrange.class };
}
}


Листинг 5 показывает фабрику адаптеров конвертирующих яблоки в апельсины. Фабрика позволяет поведение показанное на Листинге 2. Мы рассмотрим детально его поведение:
  • adaptableObject - объект, с которого мы начали - яблоко. adaptableObject - всегда экземпляр объекта;
  • adapterType - объект к которому мы адаптируем - апельсин. adapterType - всегда имеет тип class, а не экземпляр объекта.
  • Список адаптеров - это список типов классов к которым данная фабрика может адптировать объекты. В нашем случае - это только апельсины.
Мы должны регистрировать фабрику в платформе Eclipse для того, чтобы она была полезной. Листинг 6 показывает регистрационную запись из файла манифеста plug'in. Точка расширения- org.eclipse.core.runtime.adapters. Это то место, в котором я прошу быть максимально внимательным. adaptableType - это то, что из чего мы адаптируем. В нашем случае - яблоки.adapter - то во что мы адаптируем. В этом случае - апельсины.

Листинг 6 Регистрация фабрики адаптеров

<extension
point="org.eclipse.core.runtime.adapters">
<factory
adaptableType="com.jeffricker.fruit.apples.IApple"
class="com.jeffricker.fruit.OrangeAdapterFactory">;
<adapter
type="com.jeffricker.fruit.oranges.IOrange">
<⁄adapter>
<⁄factory>
<⁄extension>


Допускается несколько записей adapter на одну фабрику. Адаптеры перечисленные в точке расширения должны быть такими-же как те, которые предоставляются методом getAdapterList() в фабрике адаптера.
Если мы вместе посмотрим на листинг и проследим логику, то адаптеры станут понятнее.
1. Мы создаём экземпляр объекта Macintosh.

IApple macintosh = new Macintosh();

2. Мы просим Macintosh адаптироваться к IOrange

IOrange orange = (IOrange) macintosh.getAdapter(IOrange.class);

3. Объект Macintosh переадресует запрос платформе

public Object getAdapter(Class adapter){
return Platform.getAdapterManager().getAdapter(this, adapter);
}

4. Платформа находит в реестре подходящую фабрику адаптеров

<factory
adaptableType="com.jeffricker.fruit.apples.IApple"
class="com.jeffricker.fruit.OrangeAdapterFactory">
<adapter type="com.jeffricker.fruit.oranges.IOrange"⁄>
<⁄factory>

5. Платформа вызывает метод фабрики, передавая в него экземпляр объекта Macintosh и тип класса IOrange.
6. Фабрика адаптеров создаёт экземпляр объекта Mandarin

public Object getAdapter(Object adaptableObject, Class adapterType) {
if (adapterType == IOrange.class) {
if (adaptableObject instanceof Macintosh) {
return new Mandarin();
}
}
return null;
}

Точкой преткновения для меня является параметр adaptableType в точке расширения. В интерфейсе фабрики адаптеров на это нет указания. Это скрыто внутри логики метода getAdapter() фабрики. Его присутсвие в реестре становится понятным, когда вы начинаете думать об этом. Мы запрашиваем платформу найти адаптер для объекта Macintosh. Фабрики должны быть каким-либо способом привязаны к иерархии классов Macintosh. В нашем случае фабрика зарегистрирована с IApples. Рисунок 1 показывает взаимосвязи между объявлениями в реестре точки расширения и классом фабрики адаптера.


Рисунок 1. Взаимосвязи между фабрикой и точкой расширения

Пример поставщика представления


Адаптирование яблок к апельсинам безусловно глупый пример, но я расширил пример до чего-то более уместного. В Листинге 3 я показал адаптацию объекта яблока к ILabelProvider - интерфейсу, который используется компонентами JFace для отображения. Фабрика для этих целей показана на Листинге 7. Регистрация приведена в Листинге 8 и набросок поставщиков показан в листинге 9.
Если вы посмотрите классы поставщиков сгенерированные с помощью Eclipse Modeling Framework (EMF), то увидите, что концепция данного примера следует их логике.

Листинг 7. Фабрика поставщика фруктов для отображения фруктов в компонентах JFace

package com.jeffricker.fruit.provider;
import org.eclipse.core.runtime.IAdapterFactory;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import com.jeffricker.fruit.apples.IApple;
import com.jeffricker.fruit.oranges.IOrange;
public class FruitProviderAdapterFactory implements IAdapterFactory {
private AppleProvider appleProvider;
private OrangeProvider orangeProvider;
/** The supported types that we can adapt to */
private static final Class[] TYPES = {
FruitProvider.class, ILabelProvider.class, IContentProvider.class };
public Object getAdapter(Object adaptableObject, Class adapterType) {
if ((adapterType == FruitProvider.class)||
(adapterType == ILabelProvider.class)||
(adapterType == IContentProvider.class)){
if (adaptableObject instanceof IApple)
return getAppleProvider();
if (adaptableObject instanceof IOrange)
return getOrangeProvider();
}
return null;
}
public Class[] getAdapterList() {
return TYPES;
}
protected AppleProvider getAppleProvider(){
if (appleProvider == null)
appleProvider = new AppleProvider();
return appleProvider;
}
protected OrangeProvider getOrangeProvider(){
if (orangeProvider == null)
orangeProvider = new OrangeProvider();
return orangeProvider;
}


Листинг 8. Регистрация фабрики адаптера провайдера фруктов

<extension
point="org.eclipse.core.runtime.adapters">
<factory
adaptableType="com.jeffricker.fruit.IFruit"
class="com.jeffricker.fruit.provider.FruitProviderAdapterFactory">
<adapter
type="com.jeffricker.fruit.provider.FruitProvider">
<⁄adapter>
<adapter
type="org.eclipse.jface.viewers.IContentProvider">
<⁄adapter>
<adapter
type="org.eclipse.jface.viewers.ILabelProvider">
<⁄adapter>
<⁄factory>
<⁄extension>


Листинг 9. Классы поставщиков

package com.jeffricker.fruit.provider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
public abstract class FruitProvider implements
ILabelProvider, IContentProvider {
...
}
/**
* Provides the display of IApple objects
*/
public class AppleProvider extends FruitProvider{
public String getText(Object element){
...
}
public Image getIcon(Object element){
...
}
}
/**
* Provides the display of IOrange objects
*/
public class OrangeProvider extends FruitProvider {
...
}

Реальный пример


Eclipse Communication Framework (ECF) начинался как способ встраивания механизма сообщений в платформу Eclipse. Но в итоге он был расширен для реализации множества способов совместного использования данных в Eclipse Rich Client Platform (RCP).
ECF начинался с простого интерфейса называемого контейнер и использования шаблона Eclipse - IAdaptable для достижения кокретной функцинальности. Если мы будем использовать ECF для встроенного обмена сообщениями, то мы должны сфокусироваться на адаптации контейнера к presence и другим интерфейсам.
Листинг 10 показывает как создать ECF контейнер. Контейнер предоставляет основные виды обработки любых типов протоколов уровня сессий. Листинг 11 показывает как адаптировать контейнер к managing presence, основной особенности встроенного обмена сообщениями. Шаблон контейнер-адаптер отделяет протоколы уровня сессий от служб предоставляемых поверх этих протоколов.

Листинг 10. Создание ECF соединения

// make container instance
IContainer container = ContainerFactory.getDefault()
.createContainer("ecf.xmpp");
// make targetID
ID newID = IDFactory.getDefault()
.createID("ecf.xmpp","slewis@ecf1.osuosl.org");
// then connect to targetID with null authentication data
container.connect(targetID,null);


Листинг 11. Адаптирование контейнера к необходимой функциональности

IPresenceContainer presence = (IPresenceContainer)container
.getAdapter(IPresenceContainer.class);
if (presence != null) {
// The container DOES expose IPresenceContainer capabilities
} else {
// The container does NOT expose IPresenceContainer capabilities
}


Возможности широки. Например, мы можем создать собственный адаптер называемый IMarketDataContainer, который предоставляет потоковые рыночные данные. Мы можем создать его также как IPresenceContainer. Как показано в Листинге 12, различные поставщики рыночных данных могут иметь различные протоколы уровня сессий, например собственные закрытые протоколы, но шаблон контейнер-адаптер позволит нам включить всех их в Eclipse RCP одним и тем-же способом.

Листинг 12. Новые типы контейнеров для ECF

IContainer container = ContainerFactory.getDefault()
.createContainer("md.nyse");
ID newID = IDFactory.getDefault().createID("md.nyse","feed@jeffricker.com");
container.connect(targetID,null);
IMarketDataContainer marketData = (IMarketDataContainer)container
.getAdapter(IMarketDataContainer.class);


Шаблон адаптеров является мощным инструментом, который используется повсюду в платформе Eclipse. I hope with the hands-on, hand holding explanation in this article that you can now unleash that power in your own RCP applications.