PanelGroupControlSupport.java

  1. /*
  2.  * *************************************************************************************************************************************************************
  3.  *
  4.  * SteelBlue: DCI User Interfaces
  5.  * http://tidalwave.it/projects/steelblue
  6.  *
  7.  * Copyright (C) 2015 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
  8.  *
  9.  * *************************************************************************************************************************************************************
  10.  *
  11.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
  12.  * You may obtain a copy of the License at
  13.  *
  14.  *     http://www.apache.org/licenses/LICENSE-2.0
  15.  *
  16.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  17.  * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
  18.  *
  19.  * *************************************************************************************************************************************************************
  20.  *
  21.  * git clone https://bitbucket.org/tidalwave/steelblue-src
  22.  * git clone https://github.com/tidalwave-it/steelblue-src
  23.  *
  24.  * *************************************************************************************************************************************************************
  25.  */
  26. package it.tidalwave.ui.core.spi;

  27. import jakarta.annotation.Nonnull;
  28. import jakarta.annotation.PostConstruct;
  29. import jakarta.annotation.PreDestroy;
  30. import java.util.ArrayList;
  31. import java.util.Collection;
  32. import java.util.HashMap;
  33. import java.util.List;
  34. import java.util.Map;
  35. import java.util.Optional;
  36. import java.util.function.Function;
  37. import org.springframework.beans.factory.BeanFactory;
  38. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  39. import it.tidalwave.ui.core.PanelGroupControl;
  40. import it.tidalwave.ui.core.PanelGroupProvider;
  41. import it.tidalwave.ui.core.message.PanelShowRequest;
  42. import org.apiguardian.api.API;
  43. import it.tidalwave.util.As;
  44. import it.tidalwave.util.annotation.VisibleForTesting;
  45. import it.tidalwave.messagebus.MessageBus;
  46. import lombok.experimental.Delegate;
  47. import lombok.extern.slf4j.Slf4j;
  48. import static org.apiguardian.api.API.Status.EXPERIMENTAL;
  49. import static java.util.stream.Collectors.*;
  50. import static it.tidalwave.util.ShortNames.shortIds;

  51. /***************************************************************************************************************************************************************
  52.  *
  53.  * A support implementation of {@link PanelGroupControl}.
  54.  *
  55.  * @param   <T> the type of the control
  56.  * @param   <S> the type of the top container
  57.  * @since       2.0-ALPHA-3
  58.  * @author      Fabrizio Giudici
  59.  *
  60.  **************************************************************************************************************************************************************/
  61. @API(status = EXPERIMENTAL)
  62. @Slf4j @SuppressWarnings("this-escape")
  63. public abstract class PanelGroupControlSupport<T, S> implements As, PanelGroupControl<T>
  64.   {
  65.     @Delegate @Nonnull
  66.     private final As delegate = As.forObject(this);

  67.     @Nonnull
  68.     private final Function<As, Collection<PanelGroupProvider<S>>> pgProvider;

  69.     /** The message bus, if present on the system. */
  70.     @Nonnull
  71.     private final Optional<MessageBus> messageBus;

  72.     /** The listener to the message bus. */
  73.     private final MessageBus.Listener<PanelShowRequest> messageListener = this::onShowRequest;

  74.     /***********************************************************************************************************************************************************
  75.      * This class doesn't rely on the @SimpleSubscriber/@ListensTo annotations to avoid having a dependency on the MessageBus runtime, so it dynamically
  76.      * queries a {@link BeanFactory}.
  77.      **********************************************************************************************************************************************************/
  78.     @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
  79.     protected PanelGroupControlSupport (@Nonnull final BeanFactory beanFactory, @Nonnull final String messageBusBeanName)
  80.       {
  81.         this(PanelGroupControlSupport::defaultPanelGroupProviders, beanFactory, messageBusBeanName);
  82.       }

  83.     /***********************************************************************************************************************************************************
  84.      * Constructor for tets.
  85.      **********************************************************************************************************************************************************/
  86.     @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
  87.     protected PanelGroupControlSupport (@Nonnull final Function<As, Collection<PanelGroupProvider<S>>> pgProvider,
  88.                                         @Nonnull final BeanFactory beanFactory,
  89.                                         @Nonnull final String messageBusBeanName)
  90.       {
  91.         this.pgProvider = pgProvider;
  92.         messageBus = getMessageBus(beanFactory, messageBusBeanName);
  93.       }

  94.     /***********************************************************************************************************************************************************
  95.      * {@inheritDoc}
  96.      **********************************************************************************************************************************************************/
  97.     @Override
  98.     public void setup (@Nonnull final Configuration<T> configuration)
  99.       {
  100.         final var topContainersByGroup = configuration.getTopContainersByGroup();
  101.         final var providersByGroup = new HashMap<Group, List<PanelGroupProvider<S>>>();
  102.         pgProvider.apply(this).forEach(p -> providersByGroup.computeIfAbsent(p.getGroup(), ignored -> new ArrayList<>()).add(p));
  103.         log.debug("Providers by placement:");
  104.         providersByGroup.forEach((placement, provider) -> log.debug(">>>> {}: {}", placement, shortIds(provider)));
  105.         final var unboundGroups = providersByGroup.keySet().stream().filter(p -> !topContainersByGroup.containsKey(p)).collect(toList());

  106.         if (!unboundGroups.isEmpty())
  107.           {
  108.             throw new IllegalArgumentException("No top container(s) provider for " + unboundGroups);
  109.           }

  110.         providersByGroup.forEach((group, providers) ->
  111.             assemble(group, providers, topContainersByGroup.get(group), configuration.getGroupOptions(), configuration.getOptions()));
  112.       }

  113.     /***********************************************************************************************************************************************************
  114.      * Assemble a set of panes for the given group.
  115.      * @param   group               the {@code Group}
  116.      * @param   panelProviders      the {@code PanelGroupProvider}s associated to the given {@code Group}
  117.      * @param   topContainer        the top container
  118.      * @param   groupOptions        options for each group
  119.      * @param   options             options for doing the job
  120.      **********************************************************************************************************************************************************/
  121.     protected abstract void assemble (@Nonnull final Group group,
  122.                                       @Nonnull final List<? extends PanelGroupProvider<S>> panelProviders,
  123.                                       @Nonnull final T topContainer,
  124.                                       @Nonnull final Map<Group, List<Options>> groupOptions,
  125.                                       @Nonnull final List<Options> options);

  126.     /***********************************************************************************************************************************************************
  127.      *
  128.      **********************************************************************************************************************************************************/
  129.     protected abstract void onShowRequest (@Nonnull PanelShowRequest panelShowRequest);

  130.     /***********************************************************************************************************************************************************
  131.      *
  132.      **********************************************************************************************************************************************************/
  133.     protected void publish (@Nonnull final Object message)
  134.       {
  135.         messageBus.ifPresent(mb -> mb.publish(message));
  136.       }

  137.     /***********************************************************************************************************************************************************
  138.      *
  139.      **********************************************************************************************************************************************************/
  140.     @PostConstruct
  141.     @VisibleForTesting void initialize()
  142.       {
  143.         messageBus.ifPresent(mb -> mb.subscribe(PanelShowRequest.class, messageListener));
  144.       }

  145.     /***********************************************************************************************************************************************************
  146.      *
  147.      **********************************************************************************************************************************************************/
  148.     @PreDestroy
  149.     @VisibleForTesting void destroy()
  150.       {
  151.         messageBus.ifPresent(mb -> mb.unsubscribe(messageListener));
  152.       }

  153.     /***********************************************************************************************************************************************************
  154.      *
  155.      **********************************************************************************************************************************************************/
  156.     @Nonnull
  157.     private static Optional<MessageBus> getMessageBus (@Nonnull final BeanFactory beanFactory, @Nonnull final String messageBusBeanName)
  158.       {
  159.         if (beanFactory.containsBean(messageBusBeanName))
  160.           {
  161.             return Optional.of(beanFactory.getBean(messageBusBeanName, MessageBus.class));
  162.           }
  163.         else
  164.           {
  165.             log.warn("No message bus");
  166.             return Optional.empty();
  167.           }
  168.       }

  169.     /***********************************************************************************************************************************************************
  170.      *
  171.      **********************************************************************************************************************************************************/
  172.     @Nonnull
  173.     private static <S> Collection<PanelGroupProvider<S>> defaultPanelGroupProviders (@Nonnull final As as)
  174.       {
  175.         return as.asMany(As.<PanelGroupProvider<S>>type(PanelGroupProvider.class));
  176.       }
  177.   }