вторник, 20 октября 2009 г.

IAdaptable - одно из основных понятий Eclipse Core


Как я и обещал, постепенно от введения в OSGi мы переходим в сторону рассмотрения непосредственно Eclipse Platform. Впрочем, об Eclipse Rich Client Platform (или даже Eclipse Rich Ajax Platform) речь пока не идет, пока будем знакомиться только с Eclipse Core. Дело в том, что для последующего рассмотрения возможностей того же Eclipse Communication Framework, невозможно оставаться только в рамках платформы OSGi.

Впрочем, это еще не значит, что я не буду больше писать про OSGi вообще (в том числе и про Equinox, как одну из реализаций OSGi).

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

Ядро состоит из следующих основных бандлов:

org.eclipse.core.contenttype - поддерживает определение и управление типами файлового контента (в частности - MIME-типами).

org.eclipse.core.expression - содержит реализацию основанного на XML языка выражений, который используется для декларативного определения точек расширения.

org.eclipse.core.filesystem - общее API работы с файловой системой.

org.eclipse.core.jobs - обеспечивает инфраструктуру параллельного исполнения кода для Eclipse

org.eclipse.core.resource - содержит средства управления ресурсами: проектами, каталогами и файлами.

org.eclipse.core.runtime - обеспечивает основанный на Equinox рантайм для работы всех остальных бандлов и приложений. Является сердцем Eclipse Core.

Так же к Ядру можно отнести org.eclipse.equinox.registry - определяет так называемый реестр точек расширения. Точки расширения - специальные записи на XML-подобном языке, определяющие механизм взаимодействия бандлов. Используются еще с тех пор, когда Eclipse не был основан на OSGi. Как-нибудь поговорим о точках расширения подробнее.

Теперь можно перейти непосредственно к разговору об IAdaptable, определенному в бандле org.eclipse.core.runtime и являющемуся одним из краеугольных механизмов всей платформы.


Как известно, Java - язык со строгой типизацией, т.е. каждый объект имеет связанный с ним тип. Существует два вида типов: тип времени определения переменной и тип времени ее исполнения. Чтобы можно было вызвать некоторый метод, он должен присутствовать в типе времени определения. Для примера рассмотрим такой код:

List list = new ArrayList();

list.add("data");       // this is OK, list is valid

list.ensureCapacity(4); // this is not, ensureCapacity() is ArrayList only


Чтобы вызвать метод, определенный в классе ArrayList, нужно сделать приведение типов. Так как в данном случае List реализуется классом ArrayList и мы это знаем, то можно воспользоваться стандартными средствами Java:

((ArrayList) list).ensureCapacity(4);


Можно даже проверить, является ли list переменной типа ArrayList с помощью instanceof. Но как поступить, если нам нужно сделать приведение между несвязанными типами? Например мы хотим привести переменную, реализующую интерфейс Map к типу List? Здесь нам на помощь и приходит IAdaptable:

IAdaptable adaptable = new HashMap();

List list = (List)adaptable.getAdapter(List.class);


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

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

@SuppressWarnings("unchecked")

    @Override

    public Object getAdapter(Class clazz) {

        if (clazz == List.class)

        {

            List list = new ArrayList(this.size());

            list.addAll(this.values());

            return list;

        }



                                               // ...



        return null;

    }


Если ни одно условие не срабатывает (т.е. запросили приведение к неподдерживаемому типу), то нужно возвратить null. Это кстати определяет и использование IAdaptable: не стоит забывать, что может быть возвращен и null.

Получается, что если мы хотим добавить новый адаптер, т.е. обеспечить приведение интересующего нас класса к еще какому-нибудь типу, то нам нужно вносить изменения в этот класс. Это не всегда возможно (у нас может и не быть исходников этого класса) и/или вредно с архитектурной точки зрения (в частности - приведение сущности к GUI-типу, в этом случае в класс сущности нужно добавить ссылки на GUI-тип, что приведет к невозможности использовать сущность без GUI). Однако, в Eclipse Core есть решение этой проблемы: PlatformObject.

PlatformObject - абстрактный класс, реализующий интерфейс IAdaptable и содержащий следующий метод getAdapter:

@Override

public Object getAdapter(Class clazz) {

        return Platform.getAdapterManager().getAdapter(this, clazz);

}

   


AdapterManager представляет собой большой Map в котором для каждого класса хранятся его адаптеры. Адаптеры создаются с помощью фабрик, реализующих интерфейс IAdapterFactory, которые регистрируются в AdapterManager. Регистрироваться фабрики могут или в активаторе бандла, или через точку расширения org.eclipse.core.runtime.adapters.

Давайте напишем фабрику, создающую адаптер для приведения типа Map к типу List:

package name.samolisov.eclipse.adaptersdemo.internal;



import java.util.ArrayList;

import java.util.List;

import java.util.Map;



import org.eclipse.core.runtime.IAdapterFactory;



public class MapListAdapterFactory implements IAdapterFactory {



    @SuppressWarnings("unchecked")

    private static final Class[] types = {List.class};



    @Override

    @SuppressWarnings("unchecked")

    public Object getAdapter(Object obj, Class clazz) {

        if (clazz == List.class && obj instanceof Map) {

            List result = new ArrayList();

            result.addAll(((Map) obj).values());

            return result;

        }



        return null;

    }



    @Override

    @SuppressWarnings("unchecked")

    public Class[] getAdapterList() {

        return types;

    }

}

 


Метод getAdapterList возвращает массив классов, в которые осуществляется преобразование. Метод getAdapter реализует непосредственно логику преобразования объекта obj в объект класса clazz.

Кстати, у нас ведь нет адаптируемого (т.е., реализующего интерфейс IAdaptable) класса, который бы реализовывал интерфейс Map. Самое время его написать:

package name.samolisov.eclipse.adaptersdemo;



import java.util.Collection;

import java.util.HashMap;

import java.util.Map;

import java.util.Set;



import org.eclipse.core.runtime.PlatformObject;



public class AdaptableHashMap<K, V> extends PlatformObject implements Map<K, V> {



    private Map<K, V> delegate = new HashMap<K, V>();



    @Override

    public void clear() {

        delegate.clear();

    }



    @Override

    public boolean containsKey(Object key) {

        return delegate.containsKey(key);

    }



    // ...

}

 


Теперь рассмотрим пример регистрации адаптера и его использования в активаторе бандла:

Platform.getAdapterManager().registerAdapters(new MapListAdapterFactory(), Map.class);      



AdaptableHashMap<String, String> map = new AdaptableHashMap<String, String>();

map.put("red", "#FF0000");

map.put("blue", "#0000FF");

map.put("yellow", "#FFFF00");

map.put("gray", "#C0C0C0");



List<String> result = (List) map.getAdapter(List.class);

for (String str : result)

    System.out.println(str);


Метод registerAdapters регистрирует фабрику адаптеров для класса из которого будет совершаться преобразование. Для эксперимента можете убрать эту строчку с регистрацией фабрики - при запуске получите NPE, т.к. result будет равен null.

Манифест бандла может быть следующим:

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Adaptersdemo

Bundle-SymbolicName: name.samolisov.eclipse.adaptersdemo

Bundle-Version: 1.0.0.qualifier

Bundle-Activator: name.samolisov.eclipse.adaptersdemo.Activator

Bundle-ActivationPolicy: lazy

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Import-Package: org.eclipse.core.runtime;version="3.4.0",

 org.osgi.framework;version="1.3.0"


Главное здесь - определить импорт пакета org.eclipse.core.runtime.

Возникает вопрос: а почему IAdaptable так важен для платформы Eclipse? Дело в том, что это - один из основных способов уменьшения связанности (coupling) между частями системы. В частности, при разработке плагинов для Eclipse принято отделять интерфейсные бандлы от бандлов бизнес-логики. Использование IAdaptable совместно с IAdapterManager и IAdapterFactory позволяет ввести промежуточный слой, который полностью отделяет сущности от классов GUI. В частности, мы можем определить список каких-то объектов (например - файлов) и написать адаптер для преобразования его в выпадающий список или красивое дерево. Причем, т.к. файл наследуется от PlatformObject, то он ничего не будет знать об этом адаптере, а значит и о выпадающем списке, что позволяет использовать его вне GUI. Дальше можно поиграться с вынесением адаптеров в отдельный бандл и регистрацией их в его активаторе.

Код примеров частично взят из статьи What is IAdaptable? (кстати, если вы хотите быть в курсе последних новостей из мира Eclipse и OSGi - рекомендую блог и твиттер автора).

Скачать примеры к статье (исходники, class-файлы и библиотеки. Zip, 3.8 Mб)

Понравилось сообщение - подпишитесь на блог или читайте меня в twitter

1 комментарий:

Eugene Schava комментирует...

Очень напоминает QueryInterface в COM

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!