org.nuiton.jaxx.runtime.swing.wizard.ext.WizardExtModel Maven / Gradle / Ivy
The newest version!
/*
* #%L
* JAXX :: Runtime
* %%
* Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
package org.nuiton.jaxx.runtime.swing.wizard.ext;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.nuiton.jaxx.runtime.swing.wizard.WizardModel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Un modèle de wizard avec des opérations.
*
* @param le type des étapes.
* @author Tony Chemit - [email protected]
* @since 1.3
*/
public abstract class WizardExtModel extends WizardModel {
/** Logger */
private static final Logger log = LogManager.getLogger(WizardExtModel.class);
public static final String OPERATIONS_PROPERTY_NAME = "operations";
public static final String STEP_STATE_PROPERTY_NAME = "stepState";
public static final String MODEL_STATE_PROPERTY_NAME = "modelState";
public static final String WAS_STARTED_PROPERTY_NAME = "wasStarted";
/** La liste des opérations à effectuer */
protected final Set operations;
/** le dictionnaire des modèles d'opération */
protected final Map> models;
/** Pour conserver les états des opérations */
protected final Map stepStates;
/** L'état générale du modèle */
protected WizardState modelState;
/** un drapeau pour savoir siune opération a été lancée */
protected boolean wasStarted;
@SuppressWarnings("unchecked")
public WizardExtModel(Class stepClass, E... steps) {
super(stepClass, steps);
stepStates = new EnumMap(stepClass);
operations = (Set) EnumSet.noneOf((Class) stepClass);
models = (Map>) new EnumMap(stepClass);
}
public Set getOperations() {
return operations;
}
public WizardState getModelState() {
return modelState;
}
public boolean isWasStarted() {
return wasStarted;
}
public boolean containsOperation(E step) {
return getOperations().contains(step);
}
@SuppressWarnings("unchecked")
public E getOperation() {
return getStep() != null && getStep().isOperation() ? getStep() : null;
}
public WizardState getStepState() {
E operation = getOperation();
return getStepState(operation);
}
public WizardState getStepState(E step) {
return stepStates.get(step);
}
public void setStepState(WizardState newState) {
E operation = getOperation();
setStepState(operation, newState);
}
public void setStepState(E step, WizardState newState) {
WizardState oldValue = getStepState(step);
stepStates.put(step, newState);
if (valueAdjusting) {
return;
}
fireIndexedPropertyChange(STEP_STATE_PROPERTY_NAME, getSteps().indexOf(step), oldValue, newState);
updateModelState(step, newState);
validate();
}
public boolean[] getAccessibleSteps() {
if (log.isDebugEnabled()) {
log.debug("compute with steps " + getSteps());
}
boolean[] result = new boolean[getSteps().size()];
int index = getSteps().indexOf(getStep());
if (index != -1) {
for (int i = 0, j = steps.size(); i < j; i++) {
if (i <= index) {
// tous les onglets inferieur ou egal au courant sont accessibles
result[i] = true;
continue;
}
// les onglets au dela de l'onglet sélectionné sont accessibles
// uniquement si l'onglet precedent est accessible, valide et son etat est a SUCCESSED
E previousStep = steps.get(i - 1);
result[i] = modelState == WizardState.SUCCESSED ||
result[i - 1] &&
validate(previousStep) &&
(!previousStep.isOperation() || getStepState(previousStep) == WizardState.SUCCESSED);
}
}
if (log.isDebugEnabled()) {
log.debug("accessibles steps -------- " + Arrays.toString(result));
}
return result;
}
@Override
public void start() {
// super.start();
if (steps.isEmpty()) {
throw new IllegalStateException("can not start, no step found");
}
// update universe of steps and actions
updateUniverse();
// set first step
step = null;
E startStep = steps.get(0);
setStep(startStep);
// model is ready
setModelState(WizardState.PENDING);
validate();
}
public void cancel() {
for (E op : operations) {
if (getStepState(op) == WizardState.PENDING) {
// on annule l'opération à venir
setStepState(op, WizardState.CANCELED);
}
}
setModelState(WizardState.CANCELED);
}
public WizardExtModel addOperation(E operation) {
if (operations.contains(operation)) {
// skip add
return this;
}
log.info("Add operation " + operation);
operations.add(operation);
// mis a jour de l'univers des etapes et operations
updateUniverse();
// validation
validate();
return this;
}
public void removeOperation(E operation) {
if (!operations.contains(operation)) {
// skip remove
return;
}
operations.remove(operation);
// mis a jour de l'univers des etapes et operations
updateUniverse();
// validation
validate();
}
@Override
public void setSteps(E... steps) {
super.setSteps(steps);
if (valueAdjusting) {
return;
}
// on force la propagation de la nouvelle liste
firePropertyChange(OPERATIONS_PROPERTY_NAME, null, operations);
}
public WizardExtStepModel getStepModel(E operation) {
if (operation == null) {
return null;
}
if (!operation.isOperation()) {
throw new IllegalStateException("step [" + operation + "] is not an operation.");
}
return models.get(operation);
}
public void updateStepStates(List steps) {
int index = 0;
for (E e : steps) {
fireIndexedPropertyChange(STEP_STATE_PROPERTY_NAME, index++, null, getStepState(e));
}
firePropertyChange(MODEL_STATE_PROPERTY_NAME, null, modelState);
}
public void setErrorOnStepModel(Exception error) {
getStepModel(getOperation()).setError(error);
}
protected void setModelState(WizardState modelState) {
WizardState oldValue = this.modelState;
this.modelState = modelState;
firePropertyChange(MODEL_STATE_PROPERTY_NAME, oldValue, modelState);
if (!wasStarted) {
if ((oldValue == null || oldValue == WizardState.PENDING) && modelState == WizardState.RUNNING) {
wasStarted = true;
firePropertyChange(WAS_STARTED_PROPERTY_NAME, false, true);
}
}
}
protected void updateModelState(E step, WizardState newState) {
switch (newState) {
case RUNNING:
//le modele est occupé
setModelState(WizardState.RUNNING);
break;
case FAILED:
//le modele est en erreur
setModelState(WizardState.FAILED);
break;
case CANCELED:
//le modele devient annulé
setModelState(WizardState.CANCELED);
return;
case PENDING:
//le modele est en attente
setModelState(WizardState.PENDING);
break;
case NEED_FIX:
//le modele est en attente
setModelState(WizardState.PENDING);
break;
case SUCCESSED:
// on regarde si on peut passer le model a l'état success
boolean valid = true;
for (E o : operations) {
if (getStepState(o) != WizardState.SUCCESSED) {
valid = false;
break;
}
}
if (valid) {
setModelState(WizardState.SUCCESSED);
} else {
setModelState(WizardState.PENDING);
}
break;
}
updateStepStates(steps);
}
@Override
public void updateUniverse() {
// setValueAdjusting(true);
List oldSteps = new ArrayList<>(getSteps());
log.info("Start updateUniverse (oldSteps = " + oldSteps + ")");
E[] newSteps = updateStepUniverse();
log.info("newSteps = " + Arrays.toString(newSteps));
// do nothing if steps has not changed
boolean skip = true;
for (E newStep : newSteps) {
if (!oldSteps.contains(newStep)) {
skip = false;
}
}
if (skip && oldSteps.size() == newSteps.length) {
// same steps, so nothing to do
log.info("Steps are same, do not modify anything");
return;
}
for (WizardExtStepModel model : models.values()) {
log.info("Destroy previous model : " + model);
model.destroy();
}
models.clear();
List toAdd = new ArrayList<>(Arrays.asList(newSteps));
log.info("Will add models for " + toAdd);
Iterator itr = toAdd.iterator();
while (itr.hasNext()) {
E step = itr.next();
if (step.getModelClass() == null) {
// no model attach to the step
itr.remove();
continue;
}
if (step.isOperation()) {
// step is operation, model must be instanciate
WizardExtStepModel model = (WizardExtStepModel) step.newModel();
if (log.isInfoEnabled()) {
log.info("[" + step + "] Add primary model " + model);
}
models.put(step, model);
itr.remove();
}
// step has a model
}
if (!toAdd.isEmpty()) {
// there is some steps with model to attach
itr = toAdd.iterator();
while (itr.hasNext()) {
E step = itr.next();
Class extends WizardExtStepModel>> modelClass = step.getModelClass();
WizardExtStepModel selectedModel = null;
// find out in models a
for (WizardExtStepModel model : models.values()) {
if (modelClass.isAssignableFrom(model.getClass())) {
// find one
selectedModel = model;
break;
}
}
if (selectedModel == null) {
throw new IllegalStateException("Could not find a primary model " + modelClass + " for step [" + step + "]");
}
log.info("[" + step + "] Attach model " + selectedModel);
models.put(step, selectedModel);
itr.remove();
}
if (!toAdd.isEmpty()) {
throw new IllegalStateException("There is some step no model : " + toAdd);
}
}
setSteps(newSteps);
// on met a jour les états des étapes
stepStates.clear();
for (E step : newSteps) {
if (!stepStates.containsKey(step)) {
setStepState(step, WizardState.PENDING);
}
}
// finally set the steps (this will refire a lot)
log.info("Ending updateUniverse");
// setValueAdjusting(false);
}
protected abstract E[] updateStepUniverse();
@Override
public void destroy() {
super.destroy();
for (WizardExtStepModel model : models.values()) {
if (log.isDebugEnabled()) {
log.debug("destroy model " + model);
}
model.destroy();
}
}
protected int getOperationIndex(E operation) {
int index = 0;
for (E o : operations) {
if (o.equals(operation)) {
return index;
}
index++;
}
return -1;
}
}