понедельник, 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.