package org.sigmah.client.computation;
/*
* #%L
* Sigmah
* %%
* Copyright (C) 2010 - 2016 URD
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import com.allen_sauer.gwt.log.client.Log;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.form.Field;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.sigmah.client.dispatch.monitor.LoadingMask;
import org.sigmah.client.ui.widget.Loadable;
import org.sigmah.client.ui.widget.form.StringField;
import org.sigmah.offline.sync.SuccessCallback;
import org.sigmah.shared.computation.Computation;
import org.sigmah.shared.computation.dependency.Dependency;
import org.sigmah.shared.computation.dependency.SingleDependency;
import org.sigmah.shared.dto.ContactDTO;
import org.sigmah.shared.dto.ContactModelDTO;
import org.sigmah.shared.dto.IsModel;
import org.sigmah.shared.dto.OrgUnitModelDTO;
import org.sigmah.shared.dto.ProjectDTO;
import org.sigmah.shared.dto.ProjectModelDTO;
import org.sigmah.shared.dto.element.ComputationElementDTO;
import org.sigmah.shared.dto.element.FlexibleElementContainer;
import org.sigmah.shared.dto.element.FlexibleElementDTO;
import org.sigmah.shared.dto.element.HistoryWrapper;
import org.sigmah.shared.dto.element.event.ValueEvent;
import org.sigmah.shared.dto.element.event.ValueHandler;
import org.sigmah.shared.dto.orgunit.OrgUnitDTO;
/**
* Manage computation element triggers.
*
* @author Raphaël Calabro (raphael.calabro@netapsys.fr)
* @since 2.1
*/
public class ComputationTriggerManager {
@Inject
private ClientValueResolver valueResolver;
private final Map<ComputationElementDTO, Computation> computations = new HashMap<ComputationElementDTO, Computation>();
private final Map<FlexibleElementDTO, List<ComputationElementDTO>> dependencies = new HashMap<FlexibleElementDTO, List<ComputationElementDTO>>();
private final Map<FlexibleElementDTO, Field<String>> components = new HashMap<FlexibleElementDTO, Field<String>>();
private final Map<Integer, ComputationElementDTO> elementsWithHandlers = new HashMap<Integer, ComputationElementDTO>();
private FlexibleElementContainer container;
/**
* Prepare the trigger manager.
*
* @param project
* Project to display.
*/
public void prepareForProject(final ProjectDTO project) {
this.container = project;
clearMaps();
final ProjectModelDTO model = project.getProjectModel();
for (final ProjectDTO.LocalizedElement<ComputationElementDTO> localizedElement : project.getLocalizedElements(ComputationElementDTO.class)) {
final ComputationElementDTO computationElement = localizedElement.getElement();
prepareForComputationElement(computationElement, model);
}
// TODO: Chercher les ComputationField des projets liés et appeler prepareForComputationElement.
}
/**
* Prepare the trigger manager.
*
* @param orgUnit
* OrgUnit to display.
*/
public void prepareForOrgUnit(final OrgUnitDTO orgUnit) {
this.container = orgUnit;
clearMaps();
final OrgUnitModelDTO model = orgUnit.getOrgUnitModel();
for (final OrgUnitDTO.LocalizedElement localizedElement : orgUnit.getLocalizedElements(ComputationElementDTO.class)) {
final ComputationElementDTO computationElement = (ComputationElementDTO) localizedElement.getElement();
prepareForComputationElement(computationElement, model);
}
}
/**
* Prepare the trigger manager.
*
* @param contactDTO
* Contact to display.
*/
public void prepareForContact(ContactDTO contactDTO) {
this.container = contactDTO;
clearMaps();
for (final ContactDTO.LocalizedElement localizedElement : contactDTO.getLocalizedElements(ComputationElementDTO.class)) {
final ComputationElementDTO computationElement = (ComputationElementDTO) localizedElement.getElement();
prepareForComputationElement(computationElement, contactDTO.getContactModel());
}
}
/**
* Remove the content of every maps.
*/
private void clearMaps() {
this.dependencies.clear();
this.computations.clear();
this.components.clear();
this.elementsWithHandlers.clear();
}
/**
* List the dependencies of the given element.
*
* @param computationElement
* Computation element to prepare.
* @param model
* Model of the current container.
*/
private void prepareForComputationElement(final ComputationElementDTO computationElement, final IsModel model) {
final Computation computation = computationElement.getComputationForModel(model);
computations.put(computationElement, computation);
for (final Dependency dependency : computation.getDependencies()) {
List<ComputationElementDTO> list = dependencies.get(dependency);
if (list == null) {
list = new ArrayList<ComputationElementDTO>();
if (dependency instanceof SingleDependency) {
dependencies.put(((SingleDependency) dependency).getFlexibleElement(), list);
}
}
list.add(computationElement);
}
}
/**
* Add a value change handler to the given element if it is a dependency
* of a computation.
*
* @param element
* Element to listen.
* @param component
* Component associated to the given element.
* @param modifications
* Value change list.
*/
public void listenToValueChangesOfElement(final FlexibleElementDTO element, final Component component, final List<ValueEvent> modifications) {
if (component == null) {
Log.trace("Element '" + element.getId() + "' is not accessible by the current user.");
return;
}
if (element instanceof ComputationElementDTO) {
StringField stringField = null;
if (component instanceof StringField) {
stringField = (StringField) component;
} else if (component instanceof HistoryWrapper && ((HistoryWrapper) component).getField() instanceof StringField) {
stringField = (StringField) ((HistoryWrapper) component).getField();
}
if (stringField != null) {
components.put(element, stringField);
elementsWithHandlers.put(element.getId(), (ComputationElementDTO) element);
initialUpdateIfCurrentValueIsEmpty((ComputationElementDTO) element, stringField);
}
}
final List<ComputationElementDTO> computationElements = dependencies.get(element);
if (computationElements != null) {
element.addValueHandler(new ValueHandler() {
@Override
public void onValueChange(ValueEvent event) {
updateComputations(computationElements, modifications);
}
});
}
}
/**
* Compute the value of the given element if no value has been provided.
*
* @param computationElement
* Computation element to update.
* @param component
* Component associated to the given element.
*/
private void initialUpdateIfCurrentValueIsEmpty(final ComputationElementDTO computationElement, final Field<String> field) {
if (field.getValue() == null || field.getValue().isEmpty()) {
updateComputation(computationElement, new ArrayList<ValueEvent>(), false);
}
}
/**
* Update the given computation elements.
*
* @param computationElements
* List of elements to update.
* @param modifications
* Value change list.
*/
private void updateComputations(final List<ComputationElementDTO> computationElements, final List<ValueEvent> modifications) {
if (computationElements == null) {
return;
}
for (final ComputationElementDTO computationElement : computationElements) {
updateComputation(computationElement, modifications, true);
}
}
/**
* Update the given computation element.
*
* @param computationElement
* Element to update.
* @param modifications
* Value change list.
*/
private void updateComputation(final ComputationElementDTO computationElement, final List<ValueEvent> modifications,
final boolean fireEvents) {
final Computation computation = computations.get(computationElement);
final Loadable loadable;
final Field<String> computationView = components.get(computationElement);
if (computationView != null) {
loadable = new LoadingMask(computationView);
} else {
loadable = null;
}
computation.computeValueWithModificationsAndResolver(container, modifications, valueResolver, new SuccessCallback<String>() {
@Override
public void onSuccess(String result) {
updateComputationElementWithValue(computationElement, result, modifications, fireEvents);
}
}, loadable);
}
/**
* Update the given computation element with the given value.
*
* @param computationElement
* Computation element to update.
* @param value
* Result of the computation.
* @param modifications
* Value change list.
*/
private void updateComputationElementWithValue(final ComputationElementDTO computationElement, final String value,
final List<ValueEvent> modifications, final boolean fireEvents) {
final Field<String> field = components.get(computationElement);
if (field != null) {
field.setValue(value);
if (fireEvents) {
fireValueEvent(computationElement, value);
}
} else {
// The affected computation is not displayed.
// Manually adding the value to the modifications.
modifications.add(new ValueEvent(computationElement, value));
// Manually firing the dependencies.
updateComputations(dependencies.get(computationElement), modifications);
}
}
/**
* Fire a value event for the given computation element.
*
* @param computationElement
* Modified computation field.
* @param value
* New value.
*/
private void fireValueEvent(final ComputationElementDTO computationElement, final String value) {
// Firing a value event to register the change and trigger dependencies update.
final ComputationElementDTO withHandlers = elementsWithHandlers.get(computationElement.getId());
if (withHandlers != null) {
withHandlers.fireValueEvent(value);
} else {
computationElement.fireValueEvent(value);
}
}
}