Статьи

Идиомы платформы NetBeans: подключаемый верхний компонент (часть 4)


Давайте продолжим анализ идиомы Pluggable TopComponent.
Сегодня мы рассмотрим основной класс, EnhancedTopComponent, который выступает в качестве точки соединения между участниками идиомы и средой выполнения платформы NetBeans, а также еще несколько ролей многократного использования.

Это четвертая часть этой серии. Я сделаю много ссылок на части API, описанные в предыдущих частях, которые вы должны прочитать перед тем, как продолжить: 1 , 2 , 3 . Я также рад сообщить, что соответствующий код был перемещен в проект OpenBlueSky в Кенай, и большая его часть готова для включения в виде артефактов Maven.

Ядро EnhancedTopComponent очень просто: каждый экземпляр содержит экземпляр RoleSet (который был описан в предыдущих статьях) и заполняется из файловой системы XML (layer.xml) — например, с помощью фрагмента XML, такого как:

<filesystem>
<folder name="Roles">
<folder name="it.tidalwave.geo.explorer.GeoExplorerPresentation">
<file name="DataLoader.instance">
<attr name="instanceClass" stringvalue="it.tidalwave.geo.explorer.impl.role.DefaultGeoCoderDataLoader"/>
</file>
<file name="SelectionStrategy.instance">
<attr name="instanceClass" stringvalue="it.tidalwave.geo.explorer.impl.role.AutoSelectionStrategy"/>
</file>
...
<file name="NodeView.instance">
<attr name="instanceCreate" methodvalue="it.tidalwave.netbeans.role.util.BeanFactory.createInstance"/>
<attr name="class" stringvalue="it.tidalwave.netbeans.explorer.view.EnhancedBeanTreeView"/>
<attr name="rootVisible" boolvalue="false"/>
<attr name="dropTarget" boolvalue="false"/>
<attr name="dragSource" boolvalue="false"/>
</file>
</folder>
</folder>
...
<filesystem>

Это выдержка из реальной конфигурации проекта forceTen , и, как вы можете видеть, роли могут быть созданы несколькими способами (в наиболее сложном случае некоторые значения свойств инициализируются благодаря утилите BeanFactory, которая была описана в предыдущем посте) , Я использую соглашение об именах, где роли создаются из пути «/ Roles / <topComponentId> «.

package it.tidalwave.netbeans.windows;

public abstract class EnhancedTopComponent extends TopComponent
{
@Nonnull
private final Lookup lookup;

private final RoleSet roleSet = new RoleSet();

@CheckForNull
private final String id;

public EnhancedTopComponent()
{
this(null);
}

public EnhancedTopComponent (@CheckForNull final String id)
{
this.id = id;
lookup = new ProxyLookup(roleSet.getLookup(),
Lookups.fixed(this), // needed by roles
super.getLookup());
setLayout(new BorderLayout());
}

public void addRole (@Nonnull final Object role)
{
roleSet.addRole(role);
}

public void removerole (@Nonnull final Object role)
{
roleSet.removeRole(role);
}

private void installRoles()
{
final String path = "/Roles/" + ((id != null) ? id : preferredID());
logger.finer(">>>> looking up default roles from path %s...", path);

roleSet.setInjectedLookup(getLookup()); // a subclass might have enhanced it
roleSet.setStaticRoles(Lookups.forPath(path).lookupAll(Object.class));
roleSet.initialize();

final GUIBuilder guiBuilder = roleSet.getLookup().lookup(GUIBuilder.class);

if (guiBuilder != null)
{
add(guiBuilder.createGUI(), BorderLayout.CENTER);
}
}
}

Инициализация ролей выполняется методом installRoles (). Интересным моментом является то, что он управляет особым образом ролью с именем GUIBuilder:

public interface GUIBuilder 
{
@Nonnull
public JComponent createGUI();
}

     
Это простая фабрика, которой делегировано создание пользовательского интерфейса Swing. Таким образом, вся ответственность Swing отодвигается от EnhancedTopComponent, который становится простым контроллером. Это также означает, что любой конкретный TopComponent в настольном приложении может просто наследоваться от EnhancedTopComponent и указывать пользовательский интерфейс в отдельном классе; это также означает, что существующий пользовательский интерфейс может быть заменен альтернативным, просто переопределив конфигурацию GUIFactory.

Как я писал в преамбуле этой статьи, EnhancedTopComponents действует как мост между средой выполнения платформы и ролями. С этой целью все методы жизненного цикла были реализованы следующим образом:

interface RoleRunner
{
public void run (final @Nonnull TopComponentRole role);
}

@Override
protected void componentActivated()
{
super.componentActivated();

runRoles(new RoleRunner()
{
@Override
public void run (final @Nonnull TopComponentRole role)
{
role.notifyActivated();
}
});
}

private void runRoles (final @Nonnull RoleRunner roleRunner)
{
if (!rolesInstalled)
{
installRoles();
RoleInjector.injectLookup(this, getLookup());
PostConstructorCaller.callPostConstructors(this);
rolesInstalled = true;
}

assert rolesInstalled : "roles not initialized";

for (final TopComponentRole role : getLookup().lookupAll(TopComponentRole.class))
{
roleRunner.run(role);
}
}

Каждый метод проверяет, что роли были загружены и инициализированы, а затем вызывает метод с одинаковым именем для всех зарегистрированных ролей, которые реализуют TopComponentRole:

public interface TopComponentRole
{
public void notifyActivated();

public void notifyDeactivated();

public void notifyOpened();

public void notifyClosed();

public void notifyShowing();

public void notifyHidden();
}

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

Типичной операцией, которая должна быть связана с жизненным циклом TopComponent, является загрузка данных, которые будут отображаться. С этой целью мы можем определить интерфейс и несколько классов:

public interface DataLoader
{
public void loadData();
}

public abstract class TopComponentDataLoaderStrategy extends TopComponentRoleSupport implements DataLoader
{
@Inject
private DataLoader dataLoader;

@Override
public final void loadData()
{
dataLoader.loadData();
}
}

@NotThreadSafe
public final class EagerDataLoaderStrategy extends TopComponentDataLoaderStrategy
{
private boolean initialized;

@PostConstruct
private void initialize()
{
if (!initialized)
{
loadData();
initialized = true;
}
}
}

@NotThreadSafe
public final class LazyDataLoaderStrategy extends TopComponentDataLoaderStrategy
{
private boolean initialized;

@Override
public void notifyShowing()
{
if (!initialized)
{
loadData();
initialized = true;
}
}
}

Конкретный код для загрузки данных должен быть представлен в реализации DataLoader; затем EagerDataLoaderStrategy или LazyDataLoaderStrategy должны быть настроены в layer.xml. Следующий фрагмент layer.xml снова взят из проекта forceTen:

<filesystem>
<folder name="Roles">
<folder name="it.tidalwave.geo.explorer.GeoExplorerPresentation">
<file name="DataLoader.instance">
<attr name="instanceClass" stringvalue="it.tidalwave.geo.explorer.impl.role.DefaultGeoCoderDataLoader"/>
</file>
<file name="DataLoaderStrategy.instance">
<attr name="instanceClass" stringvalue="it.tidalwave.netbeans.windows.role.LazyDataLoaderStrategy"/>
</file>
</folder>
</folder>
...
<filesystem>

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