| /******************************************************************************* |
| * Copyright (c) 2009, 2018 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * SAP AG - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.ocl.examples.impactanalyzer.instanceScope.unusedEvaluation; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EOperation; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.ocl.EvaluationEnvironment; |
| import org.eclipse.ocl.ecore.EcoreEnvironment; |
| import org.eclipse.ocl.ecore.IfExp; |
| import org.eclipse.ocl.ecore.LoopExp; |
| import org.eclipse.ocl.ecore.OCL; |
| import org.eclipse.ocl.ecore.OCLExpression; |
| import org.eclipse.ocl.ecore.OperationCallExp; |
| import org.eclipse.ocl.ecore.Variable; |
| import org.eclipse.ocl.ecore.VariableExp; |
| import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; |
| import org.eclipse.ocl.examples.impactanalyzer.ValueNotFoundException; |
| import org.eclipse.ocl.examples.impactanalyzer.deltaPropagation.PartialEvaluatorImpl; |
| import org.eclipse.ocl.examples.impactanalyzer.deltaPropagation.VariableValueNotFoundInfo; |
| import org.eclipse.ocl.examples.impactanalyzer.deltaPropagation.VariableValueNotFoundInfoImpl; |
| import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory; |
| |
| |
| |
| /** |
| * A largely immutable request to perform a (partial) evaluation (see also {@link PartialEvaluatorImpl}) of an {@link OCLExpression}, |
| * comparing the result with a given expected result or <code>invalid</code>. If the result compares equal, this indicates that |
| * the subexpression where the change occurred is not used, leading the traceback process to returning an empty set. The only |
| * modification allowed to a request is setting an inferred variable's value (see |
| * {@link #setInferredVariableValue(Variable, Object, UnusedEvaluationRequestFactory)}. Note that this assumes that <code>invalid</code> |
| * means "unused." This is the case for the currently known unused checks, in particular computing the condition of an {@link IfExp}, |
| * computing the source of a {@link LoopExp} and computing the argument of an <code>->at(...)</code> {@link OperationCallExp}. |
| * <p> |
| * |
| * The request holds a number of <em>slots</em> for variable values inferred during the traceback process. If a new variable value |
| * is inferred for which this request has a slot, the value is entered into the slot and used for the partial evaluation. |
| * <p> |
| * |
| * If a variable for which this request has a slot changes scope, a new request is created in which the slot is removed, because |
| * we can't expect to infer the value for the right dynamic scope anymore. |
| * |
| * @author Axel Uhl (D043530) |
| * |
| */ |
| public class UnusedEvaluationRequest { |
| private final Map<Variable, Object> inferredVariableValues; |
| private final Set<Variable> slots; |
| private final OCLExpression expression; |
| private final Object resultIndicatingUnused; |
| private final SemanticIdentity semanticIdentity; |
| private final Set<VariableExp> inevitableVariableUsages; |
| |
| // fields for statistical purposes |
| public static int evaluations = 0; |
| public static int evaluationsAbortedDueToUnknownVariable = 0; |
| public static int evaluationsSucceedingAndProvingUnused = 0; |
| public static int evaluationsSucceedingWithoutProvingUnused = 0; |
| |
| /** |
| * Defines "logical" equals/hashCode as opposed to enclosing class which has default, identity-based equals/hashCode. |
| * See also {@link UnusedEvaluationRequestFactory}. |
| * |
| * @author Axel Uhl (D043530) |
| * |
| */ |
| class SemanticIdentity extends AbstractUnusedEvaluationRequestValue { |
| protected final int hashCode; |
| |
| public SemanticIdentity() { |
| hashCode = computeHashCode(); |
| } |
| |
| @Override |
| public int hashCode() { |
| return hashCode; |
| } |
| |
| public Map<Variable, Object> getInferredVariableValues() { |
| return inferredVariableValues; |
| } |
| |
| public Set<Variable> getSlots() { |
| return slots; |
| } |
| |
| public OCLExpression getExpression() { |
| return expression; |
| } |
| |
| public Object getResultIndicatingUnused() { |
| return resultIndicatingUnused; |
| } |
| } |
| |
| |
| /** |
| * Must not be called from anywhere except {@link UnusedEvaluationRequestFactory#getUnusedEvaluationRequest(OCLExpression, Object, Map, Set)} |
| * because resulting instances need to be <em>managed</em> as their equality and hash code fall back to their identity. Hence, |
| * equal objects in a given scope must be guaranteed to be identical. Such a scope is defined by the use of a |
| * {@link UnusedEvaluationRequestFactory}. |
| * |
| * @param expression |
| * the expression to evaluate |
| * @param resultIndicatingUnused |
| * if <code>expression</code> evaluates to this result, this request will return <code>true</code> from its |
| * {@link #evaluate(OppositeEndFinder, OCL)} method; as a special case, <code>null</code> will be considered "equal" |
| * to an empty collection as the result of evaluating <code>expression</code> |
| * @param inferredVariableValues |
| * may be <code>null</code>. In this case, a new {@link Map} is created internally. |
| * @param slots |
| * the variables currently within their dynamic scope such that, when a value is inferred for such a variable, it |
| * is correct to assign it for use in evaluating <code>expression</code> in this request |
| */ |
| UnusedEvaluationRequest(OCLExpression expression, Object resultIndicatingUnused, |
| Map<Variable, Object> inferredVariableValues, Set<Variable> slots) { |
| this(expression, resultIndicatingUnused, inferredVariableValues, slots, |
| expression.accept(new FindAlwaysUsedVariablesVisitor())); |
| } |
| |
| /** |
| * Must not be called from anywhere except {@link UnusedEvaluationRequestFactory#getUnusedEvaluationRequest(OCLExpression, Object, Map, Set)} |
| * because resulting instances need to be <em>managed</em> as their equality and hash code fall back to their identity. Hence, |
| * equal objects in a given scope must be guaranteed to be identical. Such a scope is defined by the use of a |
| * {@link UnusedEvaluationRequestFactory}. |
| * |
| * @param expression |
| * the expression to evaluate |
| * @param resultIndicatingUnused |
| * if <code>expression</code> evaluates to this result, this request will return <code>true</code> from its |
| * {@link #evaluate(OppositeEndFinder, OCLFactory)} method; as a special case, <code>null</code> will be considered "equal" |
| * to an empty collection as the result of evaluating <code>expression</code> |
| * @param inferredVariableValues |
| * may be <code>null</code>. In this case, a new {@link Map} is created internally. |
| * @param slots |
| * the variables currently within their dynamic scope such that, when a value is inferred for such a variable, it |
| * is correct to assign it for use in evaluating <code>expression</code> in this request |
| */ |
| protected UnusedEvaluationRequest(OCLExpression expression, Object resultIndicatingUnused, |
| Map<Variable, Object> inferredVariableValues, Set<Variable> slots, Set<VariableExp> inevitableVariableUsages) { |
| this.expression = expression; |
| this.resultIndicatingUnused = resultIndicatingUnused; |
| if (inferredVariableValues == null) { |
| this.inferredVariableValues = Collections.emptyMap(); |
| } else { |
| this.inferredVariableValues = inferredVariableValues; |
| } |
| this.slots = slots; |
| this.semanticIdentity = new SemanticIdentity(); |
| this.inevitableVariableUsages = inevitableVariableUsages; |
| } |
| |
| SemanticIdentity getSemanticIdentity() { |
| return semanticIdentity; |
| } |
| |
| /** |
| * If this request holds {@link #slots slots} for any of the variables in <code>slotsToRemove</code>, a new request is created |
| * in which all those slots are removed. For the remaining slots, the {@link #inferredVariableValues inferred variable values} |
| * are copied from this request. If none of this request's slots are to be removed, this request is returned unchanged. |
| */ |
| UnusedEvaluationRequest getRequestWithSlotsRemoved(Set<Variable> slotsToRemove, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) { |
| UnusedEvaluationRequest result; |
| if (slotsToRemove == null || slotsToRemove.isEmpty()) { |
| result = this; |
| } else { |
| Set<Variable> remainingSlots = slots; |
| // iterate this way because we assume slotsToRemove.size() >> slots.size() |
| for (Iterator<Variable> i = slots.iterator(); i.hasNext();) { |
| Variable v = i.next(); |
| if (slotsToRemove.contains(v)) { |
| if (remainingSlots == slots) { // copy on write/remove |
| remainingSlots = new HashSet<Variable>(slots); |
| } |
| remainingSlots.remove(v); |
| } |
| } |
| if (remainingSlots != slots) { // it changed |
| Map<Variable, Object> remainingInferredVariableValues = new HashMap<Variable, Object>(); |
| for (Map.Entry<Variable, Object> e : inferredVariableValues.entrySet()) { |
| if (!slotsToRemove.contains(e.getKey())) { |
| remainingInferredVariableValues.put(e.getKey(), e.getValue()); |
| } |
| } |
| result = unusedEvaluationRequestFactory.getUnusedEvaluationRequest(expression, resultIndicatingUnused, |
| remainingInferredVariableValues, remainingSlots, inevitableVariableUsages); |
| } else { |
| result = this; |
| } |
| } |
| return result; |
| } |
| |
| public boolean hasOneOrMoreSlots() { |
| return slots != null && slots.size() > 0; |
| } |
| |
| public boolean hasSlotFor(Variable v) { |
| return slots != null && slots.contains(v); |
| } |
| |
| /** |
| * Sets the value for a variable just inferred. If this request doesn't have a slot for the variable whose |
| * value was inferred, the request is not updated by this call. |
| */ |
| UnusedEvaluationRequest setInferredVariableValue(Variable variable, Object value, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) { |
| UnusedEvaluationRequest result; |
| if (slots.contains(variable)) { |
| if (inferredVariableValues.containsKey(variable)) { |
| if (inferredVariableValues.get(variable) != value) { |
| throw new RuntimeException("Internal error: inferred two different values for variable " + variable |
| + " in what should have been the same dynamic scope: " |
| + inferredVariableValues.get(variable) + " vs. " + value); |
| } else { |
| result = this; |
| } |
| } else { |
| Map<Variable, Object> newInferredVariableValues = new HashMap<Variable, Object>(inferredVariableValues); |
| newInferredVariableValues.put(variable, value); |
| result = unusedEvaluationRequestFactory.getUnusedEvaluationRequest(expression, resultIndicatingUnused, |
| newInferredVariableValues, slots, inevitableVariableUsages); |
| } |
| } else { |
| result = this; |
| } |
| return result; |
| } |
| |
| /** |
| * Tries a partial evaluation of the {@link #expression} by setting the variable values inferred so far |
| * (see {@link #inferredVariableValues}) in the evaluation environment. If this succeeds, the result is compared |
| * to {@link #resultIndicatingUnused}. If successful, <code>true</code> is returned. If evaluation fails for an |
| * unknown variable, the {@link ValueNotFoundException} is simply passed through.<p> |
| * |
| * Callers should call {@link #checkValuePresentForAllRequiredVariables()} before to see if it makes sense |
| * at all to attempt an evaluation or if a {@link ValueNotFoundException} would inevitably result. This |
| * saves the effort for a failing partial evaluation. |
| */ |
| public boolean evaluate(OppositeEndFinder oppositeEndFinder, OCLFactory oclFactory) throws ValueNotFoundException { |
| // use an evaluator that doesn't even try to perform an allInstances() call because it likely would |
| // cost more than it saves |
| PartialEvaluatorNoAllInstances evaluator = createPartialEvaluatorNoAllInstances(oppositeEndFinder, oclFactory); |
| EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> env = evaluator.getOcl() |
| .getEvaluationEnvironment(); |
| Object context = null; |
| for (Map.Entry<Variable, Object> e : inferredVariableValues.entrySet()) { |
| // if "self" then use as context variable; this will set self in evaluator |
| if (e.getKey().getName().equals(EcoreEnvironment.SELF_VARIABLE_NAME)) { |
| context = e.getValue(); |
| } else { |
| env.add(e.getKey().getName(), e.getValue()); |
| } |
| } |
| Object result; |
| try { |
| evaluations++; |
| result = evaluator.evaluate(context, expression); |
| } catch (ValueNotFoundException vfne) { |
| evaluationsAbortedDueToUnknownVariable++; |
| throw vfne; |
| } catch (NoAllInstancesDuringEvaluationForUnusedCheck ex) { |
| result = ex; |
| } |
| boolean unused = !(result instanceof NoAllInstancesDuringEvaluationForUnusedCheck) && |
| ((result == null && resultIndicatingUnused == null) || |
| result == evaluator.getOcl().getEnvironment().getOCLStandardLibrary().getInvalid() || |
| (result != null && |
| // asking for empty collection is encoded by asking for null |
| (result instanceof Collection<?> && resultIndicatingUnused == null && ((Collection<?>) result).isEmpty()) || |
| result.equals(resultIndicatingUnused))); |
| if (unused) { |
| evaluationsSucceedingAndProvingUnused++; |
| } else { |
| evaluationsSucceedingWithoutProvingUnused++; |
| } |
| return unused; |
| } |
| |
| protected PartialEvaluatorNoAllInstances createPartialEvaluatorNoAllInstances(OppositeEndFinder oppositeEndFinder, |
| OCLFactory oclFactory) { |
| return new PartialEvaluatorNoAllInstances(oclFactory); |
| } |
| |
| /** |
| * Returns a {@link VariableValueNotFoundInfo} if any of the variables inevitably required by the {@link #expression} are not |
| * (yet) defined and thus saves a fruitless evaluation cycle, <code>null</code> otherwise. |
| */ |
| VariableValueNotFoundInfo checkValuePresentForAllRequiredVariables() { |
| for (VariableExp inevitableVariableUse : inevitableVariableUsages) { |
| if (!inferredVariableValues.containsKey(inevitableVariableUse.getReferredVariable())) { |
| return new VariableValueNotFoundInfoImpl(inevitableVariableUse.getReferredVariable().getName(), inevitableVariableUse); |
| } |
| } |
| return null; |
| } |
| |
| public String toString() { |
| StringBuilder result = new StringBuilder("[" + expression + " = "+resultIndicatingUnused + "] with variables ["); |
| boolean first = true; |
| for (Variable slot : slots) { |
| if (!first) { |
| result.append(","); |
| } else { |
| first = false; |
| } |
| result.append(slot.getName()); |
| result.append("="); |
| if (inferredVariableValues.containsKey(slot)) { |
| result.append(inferredVariableValues.get(slot)); |
| } else { |
| result.append("?"); |
| } |
| } |
| result.append("]"); |
| return result.toString(); |
| } |
| } |