ElementsDefinitionConstructor.java
/**
* Copyright (c) 2016 European Organisation for Nuclear Research (CERN), All Rights Reserved.
*/
package org.minifx.workbench.spring;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toSet;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import org.minifx.workbench.annotations.Footer;
import org.minifx.workbench.annotations.Icon;
import org.minifx.workbench.annotations.Name;
import org.minifx.workbench.annotations.View;
import org.minifx.workbench.domain.Perspective;
import org.minifx.workbench.domain.PerspectivePos;
import org.minifx.workbench.domain.definition.DisplayProperties;
import org.minifx.workbench.domain.definition.FooterDefinition;
import org.minifx.workbench.domain.definition.PerspectiveDefinition;
import org.minifx.workbench.domain.definition.ToolbarItemDefinition;
import org.minifx.workbench.domain.definition.ViewDefinition;
import org.minifx.workbench.nodes.FxNodeFactory;
import org.minifx.workbench.util.Names;
import org.minifx.workbench.util.Perspectives;
import org.minifx.workbench.util.Purpose;
import org.springframework.core.annotation.Order;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableListMultimap.Builder;
import com.google.common.collect.ListMultimap;
/**
* Contains all the relevant logic to bring together all the information which is required to instantiate the workbench
* elements later. In particular, this is:
* <ul>
* <li>The relation between views and perspectives (and the view position inside the perspectives)
* <li>The names of views, perspectives and footers
* <li>Icons of views perspectives and footers
* </ul>
* As a final result, this class constructs perspective definitions (see {@link #perspectives()} (which contain view
* definitions) and footer definitions (see {@link #footers()}). All of these can be used to instantiate the
* perspectives (and views) and footers.
* <p>
* This class uses a {@link WorkbenchElementsRepository} to get the collected components as well as to access the
* factory methods and annotations of the collected elements.
* <p>
* For deriving information, usually annotations are used. Those annotations are searched in the order as described in
* {@link BeanInformationRepository#from(Object)}. Based on this, the following strategies are applied in particular
* cases:
* <ul>
* <li><b>Views:</b> Each bean for which a {@link View} annotation is found, is considered as view in MiniFx. The view
* annotation describes the perspective in which the view is displayed and the position within the view. Both values are
* optional. If omitted, the view is displayed in the perspective {@link Perspectives#DEFAULT_PERSPECTIVE} at the
* position {@link Perspectives#DEFAULT_POSITION}.
* <li><b>Icons:</b> They are derived from the {@link Icon} annotation (both for views and perspectives). If none is
* specified, then perspectives will get a default icon and views will have no icon.
* <li><b>Names:</b> Names for views are derived in the following order:
* <ol>
* <li>If annotated by {@link Name}, the value from there is used.
* <li>If possible, a name from a name method of the bean is used (see {@link Names#nameFromNameMethod(Object)}.
* <li>The bean name from the spring context
* <li>If none of the above is found, the simple class name of the view is used.
* </ol>
* <li><b>Order:</b> if an {@link Order} annotation is specified for a view (or a perspective) then it is taken into
* account.
* </ul>
*
* @author kfuchsbe
*/
public class ElementsDefinitionConstructor {
private final WorkbenchElementsRepository repository;
private final BeanInformationExtractor extractor;
private final FxNodeFactory fxNodeFactory;
/**
* Constructor which requires an elements repository, which will be used to look up the collected elements as well
* as annotations on them and factory methods.
*
* @param elementsRepository the repository to use
* @param beanInformationExtractor the instance who knows about the information of the beans
* @param fxNodeFactory the factory to create java fx nodes
* @throws NullPointerException if the given repository is {@code null}
*/
public ElementsDefinitionConstructor(WorkbenchElementsRepository elementsRepository,
BeanInformationExtractor beanInformationExtractor, FxNodeFactory fxNodeFactory) {
this.repository = requireNonNull(elementsRepository, "elementsRepository must not be null");
this.extractor = requireNonNull(beanInformationExtractor, "beanInformationExtractor must not be null");
this.fxNodeFactory = requireNonNull(fxNodeFactory, "fxNodeFactory must not be null");
}
/**
* Returns a perspective definition for each perspective used by at least one of the views found in the repository.
*
* @return all perspective definitions as derived from the views in the repository
*/
public Set<PerspectiveDefinition> perspectives() {
return definitionsFrom(mapToPerspective(repository.views()));
}
/**
* Returns a footer definition for each footer found in the repository
*
* @return all footer definitions
*/
public Set<FooterDefinition> footers() {
return footerDefinitionsFrom(repository.footers());
}
public Set<ToolbarItemDefinition> toolbarItems() {
return toolbarItemDefinitionsFrom(repository.toolbarItems());
}
@VisibleForTesting
PerspectivePos viewPosFor(Object view) {
requireNonNull(view, "view must not be null");
return viewAnnotation(view).map(View::at).orElse(Perspectives.DEFAULT_POSITION);
}
@VisibleForTesting
Class<? extends Perspective> perspectiveFor(Object view) {
requireNonNull(view, "view must not be null");
Optional<Class<? extends Perspective>> p = viewAnnotation(view).map(View::in);
return p.orElse(Perspectives.DEFAULT_PERSPECTIVE);
}
private Optional<View> viewAnnotation(Object view) {
return repository.from(view).getAnnotation(View.class);
}
private boolean alwaysShowTabsFromView(Object view) {
return viewAnnotation(view).map(View::enforceTab).orElse(false);
}
private boolean alwaysShowTabsFromFooter(Object footer) {
return footerAnnotation(footer).map(Footer::enforceTab).orElse(false);
}
private Optional<Footer> footerAnnotation(Object footer) {
return repository.from(footer).getAnnotation(Footer.class);
}
private ListMultimap<Class<? extends Perspective>, Object> mapToPerspective(Collection<Object> views) {
Builder<Class<? extends Perspective>, Object> perspectiveToViewBuilder = ImmutableListMultimap.builder();
for (Object view : views) {
perspectiveToViewBuilder.put(perspectiveFor(view), view);
}
return perspectiveToViewBuilder.build();
}
private Set<PerspectiveDefinition> definitionsFrom(
ListMultimap<Class<? extends Perspective>, Object> mapToPerspective) {
return mapToPerspective.asMap().entrySet().stream().map(e -> toPerspectiveDefinition(e.getKey(), e.getValue()))
.collect(toSet());
}
private PerspectiveDefinition toPerspectiveDefinition(Class<? extends Perspective> perspective,
Collection<Object> allViews) {
return new PerspectiveDefinition(perspective, Perspectives.perspectiveDisplayProperties(perspective),
viewDefinitionsFrom(allViews));
}
private Set<ViewDefinition> viewDefinitionsFrom(Collection<Object> allViews) {
return allViews.stream().map(this::toViewDefinition).collect(toSet());
}
private Set<FooterDefinition> footerDefinitionsFrom(Collection<Object> allViews) {
return allViews.stream().map(this::toFooterDefinition).collect(toSet());
}
private Set<ToolbarItemDefinition> toolbarItemDefinitionsFrom(Collection<Object> toolbarItems) {
return toolbarItems.stream().map(this::toToolbarItemDefinition).collect(toSet());
}
private ToolbarItemDefinition toToolbarItemDefinition(Object tbItem) {
return new ToolbarItemDefinition(fxNodeFactory.fxNodeFrom(tbItem), extractor.orderFrom(tbItem));
}
private ViewDefinition toViewDefinition(Object view) {
return new ViewDefinition(fxNodeFactory.fxNodeFrom(view), viewPosFor(view),
displayPropertiesFrom(view, Purpose.VIEW), alwaysShowTabsFromView(view));
}
private FooterDefinition toFooterDefinition(Object footer) {
return new FooterDefinition(fxNodeFactory.fxNodeFrom(footer), displayPropertiesFrom(footer, Purpose.VIEW),
alwaysShowTabsFromFooter(footer));
}
public DisplayProperties displayPropertiesFrom(Object view, Purpose purpose) {
return this.extractor.displayPropertiesFrom(view, purpose);
}
}