/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jspresso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.view;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.jspresso.framework.action.ActionContextConstants;
import org.jspresso.framework.action.IAction;
import org.jspresso.framework.action.IActionHandler;
import org.jspresso.framework.action.IActionHandlerAware;
import org.jspresso.framework.binding.ICollectionConnector;
import org.jspresso.framework.binding.ICollectionConnectorProvider;
import org.jspresso.framework.binding.IValueConnector;
import org.jspresso.framework.model.IModelChangeListener;
import org.jspresso.framework.model.ModelChangeEvent;
import org.jspresso.framework.model.descriptor.ICollectionDescriptor;
import org.jspresso.framework.model.descriptor.IModelDescriptor;
import org.jspresso.framework.model.descriptor.IPropertyDescriptor;
import org.jspresso.framework.security.ISecurable;
import org.jspresso.framework.security.ISecurityHandlerAware;
import org.jspresso.framework.security.ISubjectAware;
import org.jspresso.framework.util.event.IItemSelectable;
import org.jspresso.framework.util.event.IItemSelectionListener;
import org.jspresso.framework.util.event.ISelectionChangeListener;
import org.jspresso.framework.util.event.IValueChangeListener;
import org.jspresso.framework.util.event.ItemSelectionEvent;
import org.jspresso.framework.util.event.SelectionChangeEvent;
import org.jspresso.framework.util.event.ValueChangeEvent;
import org.jspresso.framework.util.gate.GateHelper;
import org.jspresso.framework.util.gate.IGate;
import org.jspresso.framework.util.gate.IModelGate;
import org.jspresso.framework.view.action.IDisplayableAction;
/**
* Abstract base class for action factories.
*
* @author Vincent Vandenschrick
* @param <E>
* the actual action class created.
* @param <F>
* the actual component class the created actions are installed in.
* @param <G>
* the actual icon class the created actions support.
*/
public abstract class AbstractActionFactory<E, F, G> implements
IActionFactory<E, F> {
private IIconFactory<G> iconFactory;
private boolean liveDebugUI = false;
/**
* {@inheritDoc}
*/
@Override
public E createAction(IAction action, IActionHandler actionHandler,
IView<F> view, Locale locale) {
if (action == null) {
return null;
}
return createAction(action, null, actionHandler, view, locale);
}
/**
* Creates the initial action context.
*
* @param actionHandler
* the action handler.
* @param view
* the view.
* @param viewConnector
* the view connector.
* @param actionCommand
* the action command.
* @param actionWidget
* the widget this action was triggered from.
* @return the initial action context.
*/
@Override
public Map<String, Object> createActionContext(IActionHandler actionHandler,
IView<F> view, IValueConnector viewConnector, String actionCommand,
F actionWidget) {
Map<String, Object> actionContext = new HashMap<>();
IModelDescriptor modelDescriptor = null;
F sourceComponent = null;
if (view != null) {
if (view.getDescriptor() != null) {
modelDescriptor = view.getDescriptor().getModelDescriptor();
}
sourceComponent = view.getPeer();
}
IValueConnector refinedViewConnector = viewConnector;
if (modelDescriptor instanceof ICollectionDescriptor<?>) {
refinedViewConnector = ((ICollectionConnectorProvider) viewConnector)
.getCollectionConnector();
}
actionContext.put(ActionContextConstants.VIEW, view);
actionContext.put(ActionContextConstants.MODEL_DESCRIPTOR, modelDescriptor);
actionContext.put(ActionContextConstants.SOURCE_COMPONENT, sourceComponent);
actionContext.put(ActionContextConstants.VIEW_CONNECTOR,
refinedViewConnector);
actionContext.put(ActionContextConstants.ACTION_COMMAND, actionCommand);
actionContext.put(ActionContextConstants.ACTION_WIDGET, actionWidget);
return actionContext;
}
/**
* Sets the iconFactory.
*
* @param iconFactory
* the iconFactory to set.
*/
public void setIconFactory(IIconFactory<G> iconFactory) {
this.iconFactory = iconFactory;
}
/**
* Creates and attach the necessary action gates.
*
* @param action
* the displayable Jspresso action.
* @param actionHandler
* the action handler.
* @param view
* the view.
* @param uiAction
* the created ui specific action.
*/
protected void attachActionGates(IDisplayableAction action,
IActionHandler actionHandler, IView<F> view, E uiAction) {
try {
actionHandler.pushToSecurityContext(action);
IModelDescriptor modelDescriptor = null;
IValueConnector viewConnector = null;
if (view != null) {
if (view.getDescriptor() != null) {
modelDescriptor = view.getDescriptor().getModelDescriptor();
}
viewConnector = view.getConnector();
}
Collection<IGate> actionabilityGates = action.getActionabilityGates();
if (actionabilityGates != null) {
Collection<IGate> clonedGates = new HashSet<>();
for (IGate gate : actionabilityGates) {
if (!(gate instanceof ISecurable)
|| actionHandler.isAccessGranted((ISecurable) gate)) {
final IGate clonedGate = gate.clone();
applyGateDependencyInjection(clonedGate, actionHandler);
if (clonedGate instanceof IModelGate && viewConnector != null) {
if (((IModelGate) clonedGate).isCollectionBased()) {
if (/*
* modelDescriptor instanceof
* ICollectionPropertyDescriptor<?>
*/viewConnector instanceof ICollectionConnectorProvider) {
((IModelGate) clonedGate).setModel(null);
// tracks children connectors selection
((ICollectionConnectorProvider) viewConnector)
.getCollectionConnector().addSelectionChangeListener(
new ISelectionChangeListener() {
@Override
public void selectionChange(SelectionChangeEvent evt) {
ICollectionConnector collConnector = (ICollectionConnector) evt
.getSource();
int[] newSelection = evt.getNewSelection();
assignCollectionBasedGateModel(clonedGate,
collConnector, newSelection);
}
});
// tracks selected children model change
((ICollectionConnectorProvider) viewConnector)
.getCollectionConnector().addValueChangeListener(
new IValueChangeListener() {
@Override
public void valueChange(ValueChangeEvent evt) {
ICollectionConnector collConnector = (ICollectionConnector) evt
.getSource();
int[] newSelection = collConnector
.getSelectedIndices();
assignCollectionBasedGateModel(clonedGate,
collConnector, newSelection);
}
});
// to respect init state
assignCollectionBasedGateModel(clonedGate,
((ICollectionConnectorProvider) viewConnector)
.getCollectionConnector(),
((ICollectionConnectorProvider) viewConnector)
.getCollectionConnector().getSelectedIndices());
} else if (viewConnector instanceof IItemSelectable) {
((IModelGate) clonedGate).setModel(null);
((IItemSelectable) viewConnector)
.addItemSelectionListener(new IItemSelectionListener() {
@Override
public void selectedItemChange(ItemSelectionEvent event) {
Object selectedItem = event.getSelectedItem();
if (selectedItem == event.getSource()) {
return;
}
assignCollectionBasedGateModel(clonedGate,
selectedItem);
}
});
// to respect init state
assignCollectionBasedGateModel(clonedGate,
((IItemSelectable) viewConnector).getSelectedItem());
} else {
bindSimpleGateModel(clonedGate, viewConnector,
modelDescriptor);
}
} else {
bindSimpleGateModel(clonedGate, viewConnector, modelDescriptor);
}
}
clonedGates.add(clonedGate);
}
}
new GatesListener(uiAction, clonedGates);
}
} finally {
actionHandler.restoreLastSecurityContextSnapshot();
}
}
/**
* Performs dependency injection on a gate.
*
* @param gate
* the gate.
* @param actionHandler
* the action handler.
*/
protected void applyGateDependencyInjection(IGate gate,
IActionHandler actionHandler) {
if (gate instanceof ISecurityHandlerAware) {
((ISecurityHandlerAware) gate).setSecurityHandler(actionHandler);
}
if (gate instanceof IActionHandlerAware) {
((IActionHandlerAware) gate).setActionHandler(actionHandler);
}
if (gate instanceof ISubjectAware) {
((ISubjectAware) gate).setSubject(actionHandler.getSubject());
}
}
private void bindSimpleGateModel(final IGate gate,
IValueConnector viewConnector, IModelDescriptor modelDescriptor) {
if (modelDescriptor instanceof IPropertyDescriptor) {
// Binds to the model provider
if (viewConnector.getModelConnector() != null
&& viewConnector.getModelConnector().getModelProvider() != null) {
((IModelGate) gate).setModel(viewConnector.getModelConnector()
.getModelProvider().getModel());
// the following disables table cell editors in swing.
// } else {
// ((IModelGate) gate).setModel(null);
}
final IModelChangeListener modelChangeListener = new IModelChangeListener() {
@Override
public void modelChange(ModelChangeEvent evt) {
((IModelGate) gate).setModel(evt.getNewValue());
}
};
viewConnector.addPropertyChangeListener(
IValueConnector.MODEL_CONNECTOR_PROPERTY,
new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
IValueConnector oldModelConnector = (IValueConnector) evt
.getOldValue();
IValueConnector newModelConnector = (IValueConnector) evt
.getNewValue();
if (oldModelConnector != null) {
oldModelConnector.getModelProvider().removeModelChangeListener(
modelChangeListener);
}
if (newModelConnector != null
&& newModelConnector.getModelProvider() != null) {
((IModelGate) gate).setModel(newModelConnector
.getModelProvider().getModel());
newModelConnector.getModelProvider().addModelChangeListener(
modelChangeListener);
// the following disables table cell editors in swing.
// } else {
// ((IModelGate) gate).setModel(null);
}
}
});
} else {
// simply binds to the value.
((IModelGate) gate).setModel(viewConnector.getConnectorValue());
viewConnector.addValueChangeListener(new IValueChangeListener() {
@Override
public void valueChange(ValueChangeEvent evt) {
((IModelGate) gate).setModel(evt.getNewValue());
}
});
}
}
/**
* Gets the iconFactory.
*
* @return the iconFactory.
*/
protected IIconFactory<G> getIconFactory() {
return iconFactory;
}
/**
* Is live debug UI structure.
*
* @return the boolean
*/
protected boolean isLiveDebugUI() {
return liveDebugUI;
}
/**
* Sets live debug UI structure.
*
* @param liveDebugUI the live debug uI structure
*/
public void setLiveDebugUI(boolean liveDebugUI) {
this.liveDebugUI = liveDebugUI;
}
private void assignCollectionBasedGateModel(final IGate gate,
ICollectionConnector collConnector, int... selectedIndices) {
Set<Object> selectedModels = null;
if (selectedIndices != null && selectedIndices.length > 0) {
selectedModels = new HashSet<>();
for (int selectedIndice : selectedIndices) {
IValueConnector childConnector = collConnector
.getChildConnector(selectedIndice);
if (childConnector != null) {
selectedModels.add(childConnector.getConnectorValue());
}
}
}
((IModelGate) gate).setModel(selectedModels);
}
private void assignCollectionBasedGateModel(final IGate gate,
Object selectedItem) {
if (selectedItem != null) {
if (selectedItem instanceof IValueConnector) {
Object connectorValue = ((IValueConnector) selectedItem)
.getConnectorValue();
if (connectorValue != null) {
((IModelGate) gate).setModel(Collections.singleton(connectorValue));
} else {
((IModelGate) gate).setModel(null);
}
} else {
((IModelGate) gate).setModel(Collections.singleton(selectedItem));
}
} else {
((IModelGate) gate).setModel(null);
}
}
/**
* Complete description with live debug uI.
*
* @param action the action
* @param i18nDescription the i 18 n description
* @return the completed action description
*/
protected String completeDescriptionWithLiveDebugUI(IAction action, String i18nDescription) {
if (isLiveDebugUI()) {
if (i18nDescription == null) {
i18nDescription = "";
} else {
i18nDescription = i18nDescription + " ";
}
i18nDescription = i18nDescription + "(Action PermId -> [" + action.getPermId() + "])";
}
return i18nDescription;
}
private final class GatesListener implements PropertyChangeListener {
private final E action;
private final Collection<IGate> gates;
/**
* Constructs a new {@code GatesListener} instance.
*
* @param action
* the action to (de)activate based on gates state.
* @param gates
* the gates that determine action state.
*/
public GatesListener(E action, Collection<IGate> gates) {
this.action = action;
this.gates = gates;
setActionEnabled(action, GateHelper.areGatesOpen(gates));
for (IGate gate : gates) {
gate.addPropertyChangeListener(IGate.OPEN_PROPERTY, this);
}
}
/**
* {@inheritDoc}
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
setActionEnabled(action, GateHelper.areGatesOpen(gates));
}
}
}