Как я и обещал, постепенно от введения в 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
list.add("data"); // this is OK, list is valid
list.ensureCapacity(4); // this is not, ensureCapacity() is ArrayList only
Чтобы вызвать метод, определенный в классе ArrayList, нужно сделать приведение типов. Так как в данном случае List реализуется классом ArrayList и мы это знаем, то можно воспользоваться стандартными средствами Java:
Можно даже проверить, является ли list переменной типа ArrayList с помощью instanceof. Но как поступить, если нам нужно сделать приведение между несвязанными типами? Например мы хотим привести переменную, реализующую интерфейс Map к типу List? Здесь нам на помощь и приходит IAdaptable:
Т.е., фактически 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;
}
@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);
}
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;
}
}
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);
}
// ...
}
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);
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"
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 комментарий:
Очень напоминает QueryInterface в COM
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!