DefaultUserActionFactory.java
/*
* *************************************************************************************************************************************************************
*
* SteelBlue: DCI User Interfaces
* http://tidalwave.it/projects/steelblue
*
* Copyright (C) 2015 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
*
* *************************************************************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*
* *************************************************************************************************************************************************************
*
* git clone https://bitbucket.org/tidalwave/steelblue-src
* git clone https://github.com/tidalwave-it/steelblue-src
*
* *************************************************************************************************************************************************************
*/
package it.tidalwave.ui.util.impl;
import jakarta.annotation.Nonnull;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Optional;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import it.tidalwave.ui.core.message.PanelHiddenNotification;
import it.tidalwave.ui.core.message.PanelShowRequest;
import it.tidalwave.ui.core.message.PanelShownNotification;
import it.tidalwave.ui.core.role.Displayable;
import it.tidalwave.ui.core.role.UserAction;
import it.tidalwave.ui.util.UserActionFactory;
import it.tidalwave.util.annotation.VisibleForTesting;
import it.tidalwave.messagebus.MessageBus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/***************************************************************************************************************************************************************
*
* The default implementation of {@link UserActionFactory}.
*
* @author Fabrizio Giudici
*
**************************************************************************************************************************************************************/
@RequiredArgsConstructor @Slf4j
public class DefaultUserActionFactory implements UserActionFactory
{
@Nonnull
private final Optional<MessageBus> messageBus;
private final IdentityHashMap<Object, UserAction> actionsByRequestor = new IdentityHashMap<>();
private final Collection<Object> currentlyShownPanels = new ArrayList<>();
/** The listener to the message bus - stored to a field otherwise weak listeners will lose it. */
private final MessageBus.Listener<PanelShownNotification> panelShownNotificationListener = this::onPanelShown;
/** The listener to the message bus - stored to a field otherwise weak listeners will lose it. */
private final MessageBus.Listener<PanelHiddenNotification> panelHiddenNotificationListener = this::onPanelHidden;
/***********************************************************************************************************************************************************
* {@inheritDoc}
**********************************************************************************************************************************************************/
@Override @Nonnull
public UserActionParameters.UserActionParametersBuilder createViewActionFor (@Nonnull final Object presentation)
{
messageBus.ifPresentOrElse(ignored -> {}, () -> log.warn("NO MESSAGEBUS, action won't work"));
return UserActionParameters.builder().callback(this::createViewAction).presentation(presentation);
}
/***********************************************************************************************************************************************************
* Registers listeners if message bus is present. Since message bus dependency is optional, we can't rely on annotations, which require Aspect support.
**********************************************************************************************************************************************************/
@PostConstruct @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
private void subscribe()
{
messageBus.ifPresent(m -> m.subscribe(PanelShownNotification.class, panelShownNotificationListener));
messageBus.ifPresent(m -> m.subscribe(PanelHiddenNotification.class, panelHiddenNotificationListener));
}
/***********************************************************************************************************************************************************
* Unregisters listeners if message bus is present. Since message bus dependency is optional, we can't rely on annotations, which require Aspect support.
**********************************************************************************************************************************************************/
@PreDestroy @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
private void unsubscribe()
{
messageBus.ifPresent(m -> m.unsubscribe(panelShownNotificationListener));
messageBus.ifPresent(m -> m.unsubscribe(panelHiddenNotificationListener));
}
/***********************************************************************************************************************************************************
* {@return a new {@UserAction} created out of the given parameters}. This method is called back by {@code build()}.
* @param parameters the parameters
**********************************************************************************************************************************************************/
@Nonnull
private UserAction createViewAction (@Nonnull final UserActionParameters parameters)
{
final var presentation = parameters.getPresentation();
final var label = parameters.getLabel();
final var menuPlacement = parameters.getComputedMenuPlacement();
final var userAction = UserAction.of(() -> requestShowPanel(presentation), List.of(Displayable.of(label), menuPlacement));
actionsByRequestor.put(presentation, userAction);
userAction.enabled().set(!currentlyShownPanels.contains(presentation));
return userAction;
}
/***********************************************************************************************************************************************************
* Receives notification that a panel has been shown.
* @param notification the notification
**********************************************************************************************************************************************************/
@VisibleForTesting void onPanelShown (final PanelShownNotification notification)
{
log.debug("onPanelShown({})", notification);
currentlyShownPanels.add(notification.getTarget());
Optional.ofNullable(actionsByRequestor.get(notification.getTarget())).ifPresent(a -> a.enabled().set(false));
// actionsByRequestor.forEach((requestor, action) -> action.enabled().set(requestor != notification.getTarget()));
}
/***********************************************************************************************************************************************************
* Receives notification that a panel has been hidden.
* @param notification the notification
**********************************************************************************************************************************************************/
@VisibleForTesting void onPanelHidden (final PanelHiddenNotification notification)
{
log.debug("onPanelHidden({})", notification);
currentlyShownPanels.remove(notification.getTarget());
Optional.ofNullable(actionsByRequestor.get(notification.getTarget())).ifPresent(a -> a.enabled().set(true));
}
/***********************************************************************************************************************************************************
* Fires a request for showing up the given presentation.
* @param presentation the presentation
**********************************************************************************************************************************************************/
private void requestShowPanel (@Nonnull final Object presentation)
{
messageBus.ifPresent(m -> m.publish(new PanelShowRequest(presentation)));
}
}