package org.sigmah.shared.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 org.sigmah.shared.computation.instruction.Variable; import org.sigmah.shared.computation.instruction.Instruction; import com.google.gwt.user.client.rpc.AsyncCallback; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.sigmah.client.ui.widget.Loadable; import org.sigmah.shared.computation.dependency.Dependency; import org.sigmah.shared.computation.dependency.SingleDependency; import org.sigmah.shared.computation.instruction.BadVariable; import org.sigmah.shared.computation.value.ComputationError; import org.sigmah.shared.computation.value.ComputedValue; import org.sigmah.shared.computation.value.ComputedValues; import org.sigmah.shared.dto.element.FlexibleElementContainer; import org.sigmah.shared.dto.element.FlexibleElementDTO; import org.sigmah.shared.dto.element.event.ValueEvent; import org.sigmah.shared.dto.element.event.ValueEventWrapper; /** * Executes rules to calculate the value of a computation element. * * @author Raphaƫl Calabro (raphael.calabro@netapsys.fr) * @since 2.1 */ public class Computation { private final List<Instruction> instructions; private Set<Dependency> dependencies; /** * Creates a new computation with the given instructions. * * @param instructions * Instructions. * * @see Computations#parse(java.lang.String, java.util.List) */ public Computation(List<Instruction> instructions) { this.instructions = instructions; } /** * Compute the value for the given container and resolver (for client-side). * * @param container * Container (project or orgunit). * @param modifications * Unsaved modifications. * @param resolver * Value resolver. * @param callback * Called when the value has been computed. * @param loadables * Element to mask during the computation. */ public void computeValueWithModificationsAndResolver(final FlexibleElementContainer container, final List<ValueEvent> modifications, final ValueResolver resolver, final AsyncCallback<String> callback, final Loadable... loadables) { final HashSet<Dependency> dependencies = new HashSet<Dependency>(getDependencies()); final HashMap<Dependency, ComputedValue> variables = new HashMap<Dependency, ComputedValue>(); for (final ValueEvent modification : modifications) { final FlexibleElementDTO source = modification.getSourceElement(); final Dependency dependency = new SingleDependency(source); variables.put(dependency, ComputedValues.from(modification.getSingleValue())); dependencies.remove(dependency); } computeValueWithVariablesDependenciesAndResolver(container.getId(), variables, dependencies, resolver, callback, loadables); } /** * Compute the value for the given container and resolver (for server-side). * * @param containerId * Identifier of the container (project or orgunit). * @param modifications * Unsaved modifications. * @param resolver * Value resolver. * @param callback * Called when the value has been computed. */ public void computeValueWithWrappersAndResolver(final int containerId, final List<ValueEventWrapper> modifications, final ValueResolver resolver, final AsyncCallback<String> callback) { final HashSet<Dependency> dependencies = new HashSet<Dependency>(getDependencies()); final HashMap<Dependency, ComputedValue> variables = new HashMap<Dependency, ComputedValue>(); for (final ValueEventWrapper modification : modifications) { final FlexibleElementDTO source = modification.getSourceElement(); final Dependency dependency = new SingleDependency(source); variables.put(dependency, ComputedValues.from(modification.getSingleValue())); dependencies.remove(dependency); } computeValueWithVariablesDependenciesAndResolver(containerId, variables, dependencies, resolver, callback); } /** * Compute the value for the given container and resolver. * * @param containerId * Identifier of the container (project or orgunit). * @param resolver * Value resolver. * @param callback * Called when the value has been computed. */ public void computeValueWithResolver(final int containerId, final ValueResolver resolver, final AsyncCallback<String> callback) { computeValueWithVariablesDependenciesAndResolver(containerId, new HashMap<Dependency, ComputedValue>(), getDependencies(), resolver, callback); } /** * Compute the value with the given values. * <p> * If some dependencies are not resolved, call the resolver to retrieves * the values. Otherwise, the computation is done directly. * </p> * * @param containerId * Identifier of the container (project or orgunit). * @param variables * Map of the already resolved variables. * @param dependencies * Not yet resolved dependencies. * @param resolver * Value resolver. * @param callback * Called when the value has been computed. * @param loadables * Element to mask during the computation. */ private void computeValueWithVariablesDependenciesAndResolver(final int containerId, final Map<Dependency, ComputedValue> variables, final Set<Dependency> dependencies, final ValueResolver resolver, final AsyncCallback<String> callback, final Loadable... loadables) { if (dependencies.isEmpty()) { // Resolver is not needed, every required value is available. callback.onSuccess(computeValue(variables).toString()); setLoading(false, loadables); } else { // Resolving values. resolver.resolve(dependencies, containerId, new AsyncCallback<Map<Dependency, ComputedValue>>() { @Override public void onFailure(Throwable caught) { callback.onFailure(caught); setLoading(false, loadables); } @Override public void onSuccess(Map<Dependency, ComputedValue> result) { variables.putAll(result); callback.onSuccess(computeValue(variables).toString()); setLoading(false, loadables); } }); } } /** * Compute the value with the given variables. * * @param variables * Values of the variables. * * @return Result of the computation. */ ComputedValue computeValue(Map<Dependency, ComputedValue> variables) { final Stack<ComputedValue> stack = new Stack<ComputedValue>(); for (final Instruction instruction : instructions) { instruction.execute(stack, variables); } return stack.peek(); } /** * Retrieves the required dependencies. * * @return A set of the dependencies required to compute this rule. */ public Set<Dependency> getDependencies() { if (dependencies != null) { return dependencies; } final LinkedHashSet<Dependency> elements = new LinkedHashSet<Dependency>(); for (final Instruction instruction : instructions) { if (instruction instanceof Variable) { elements.add(((Variable) instruction).getDependency()); } } this.dependencies = elements; return elements; } /** * Returns <code>true</code> if this computation was made from a bad formula. * * @return <code>true</code> if this computation was made from a bad formula, <code>false</code> otherwise. */ public boolean isBadFormula() { return instructions.size() == 1 && ComputedValues.from(toString()) == ComputationError.BAD_FORMULA; } /** * Find and return the bad references used in this computation. * * @return A set of every bad reference. */ public Set<String> getBadReferences() { final HashSet<String> errors = new HashSet<String>(); for (final Instruction instruction : instructions) { if (instruction instanceof BadVariable) { errors.add(((BadVariable) instruction).getReference()); } } return errors; } /** * Checks if every dependency is resolved. * * @return <code>true</code> if every dependency is resolved, * <code>false</code> otherwise. */ public boolean isResolved() { for (final Dependency dependency : getDependencies()) { if (!dependency.isResolved()) { return false; } } return true; } /** * Identify the changes that are part of the dependencies of this computation. * * @param changes * List of changes. * @return The list of changes that made the given computation breach its constraints. */ public List<ValueEventWrapper> getRelatedChanges(final List<ValueEventWrapper> changes) { final ArrayList<ValueEventWrapper> result = new ArrayList<ValueEventWrapper>(); final Set<Dependency> dependencies = getDependencies(); for (final ValueEventWrapper change : changes) { if (dependencies.contains(change.getSourceElement())) { result.add(change); } } return result; } /** * Returns this computation as a human readable <code>String</code>. * <p> * Flexible elements are referenced by their code. * </p> * * @return A String representing this computation. */ public String toHumanReadableString() { return new ComputationStringBuilder() .setHumanReadableFormat(true) .add(instructions) .toString(); } /** * {@inheritDoc} */ @Override public String toString() { return new ComputationStringBuilder() .add(instructions) .toString(); } /** * Change the loading state of the given loadables. * * @param loading State to set. * @param loadables Loadable to change. */ private void setLoading(final boolean loading, final Loadable... loadables) { for (final Loadable loadable : loadables) { if (loadable != null) { loadable.setLoading(loading); } } } }