/******************************************************************************* * Copyright (c) 2009, 2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation ******************************************************************************/ package org.eclipse.ocl.examples.impactanalyzer.instanceScope.unusedEvaluation; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.emf.ecore.EObject; import org.eclipse.ocl.ecore.Variable; import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; import org.eclipse.ocl.examples.impactanalyzer.ValueNotFoundException; import org.eclipse.ocl.examples.impactanalyzer.deltaPropagation.VariableValueNotFoundInfo; import org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache; import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory; public class UnusedEvaluationRequestSet { /** * If the result says that {@link #hasProvenUnused() unused was proven}, there won't be a {@link #getNewRequestSet() new request set} * delivered. Otherwise, the new request set delivered by {@link #getNewRequestSet()} is the request set updated by setting * a variable value in the remaining {@link UnusedEvaluationRequest}s as well as having removed those requests that were * evaluated due to the variable inference and that returned <code>false</code>. Those requests whose evaluation was triggered * by the variable inference but which failed due to another unknown variable are entered again, this time with the now * unknown variable as key. * * @author Axel Uhl (D043530) * */ public static class UnusedEvaluationResult { private final boolean provedUnused; private final UnusedEvaluationRequestSet newRequestSet; private UnusedEvaluationResult(boolean provedUnused, UnusedEvaluationRequestSet newRequestSet) { this.provedUnused = provedUnused; this.newRequestSet = newRequestSet; } public boolean hasProvenUnused() { return provedUnused; } public UnusedEvaluationRequestSet getNewRequestSet() { return newRequestSet; } public String toString() { return "[unused: "+provedUnused+", newRequestSet: "+newRequestSet+"]"; } } /** * Keys requests to evaluate an OCL expression to check whether a changed subexpression is not used. * Key is the variable whose value was unknown the last time the expression was to be evaluated. * See also {@link ValueNotFoundException}. */ private final Map<Variable, Set<UnusedEvaluationRequest>> requests; private UnusedEvaluationRequestSet(Map<Variable, Set<UnusedEvaluationRequest>> requests) { this.requests = requests; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((requests == null) ? 0 : requests.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof UnusedEvaluationRequestSet)) { return false; } UnusedEvaluationRequestSet other = (UnusedEvaluationRequestSet) obj; if (requests == null) { if (other.requests != null) { return false; } } else if (!requests.equals(other.requests)) { return false; } return true; } /** * Computes a new set of {@link UnusedEvaluationRequest}s by copying all those {@link #requests requests} not keyed by a * variable that entered or left scope. Those requests whose unknown variable left or entered scope can't be inferred anymore * along this traceback chain. Therefore, we can remove their corresponding requests. * <p> * * For all remaining requests, we remove the slots for any variable contained in <code>variablesThatLeavOrEnterScope</code>. */ public UnusedEvaluationRequestSet createReducedSet(Set<Variable> variablesThatLeaveOrEnterScope, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) { UnusedEvaluationRequestSet result = this; if (!requests.isEmpty() && !variablesThatLeaveOrEnterScope.isEmpty()) { boolean changed = false; Map<Variable, Set<UnusedEvaluationRequest>> remainingRequestsUpdated = new HashMap<Variable, Set<UnusedEvaluationRequest>>(); for (Map.Entry<Variable, Set<UnusedEvaluationRequest>> e : requests.entrySet()) { if (!variablesThatLeaveOrEnterScope.contains(e.getKey())) { // the request did not become obsolete due to its unknown variable boolean setChanged = false; UnusedEvaluationRequest[] newSet = new UnusedEvaluationRequest[e.getValue().size()]; int newSetIndex = 0; for (UnusedEvaluationRequest request : e.getValue()) { UnusedEvaluationRequest newRequest = request.getRequestWithSlotsRemoved(variablesThatLeaveOrEnterScope, unusedEvaluationRequestFactory); if (newRequest != request) { changed = true; setChanged = true; } if (!newRequest.hasOneOrMoreSlots()) { throw new RuntimeException( "Internal error: an UnusedEvaluationRequest whose unknown variable is still in scope " + "claims to have lost all its slots; the slot for the unknown variable should, however, still be there."); } newSet[newSetIndex++] = newRequest; } if (setChanged) { Set<UnusedEvaluationRequest> newSetAsSet = new HashSet<UnusedEvaluationRequest>(); for (int i=0; i<newSetIndex; i++) { newSetAsSet.add(newSet[i]); } remainingRequestsUpdated.put(e.getKey(), newSetAsSet); } else { remainingRequestsUpdated.put(e.getKey(), e.getValue()); } } else { changed = true; // the request got dropped } } if (changed) { result = new UnusedEvaluationRequestSet(remainingRequestsUpdated); } else { result = this; } } return result; } /** * Announces that the value for a variable was inferred. All contained {@link UnusedEvaluationRequest}s that have a slot for * this variable are cloned and the value inferred is stored in the cloned request. If the variable is the unknown variable * for any of the requests managed by this set, the request is re-evaluated. If the request evaluates to <code>false</code>, * it is silently removed from the new request set returned. If the request evaluates to <code>true</code>, further evaluation * is aborted immediately, and the method just returns the fact that one of the requests evaluated to <code>true</code>. If * the request evaluation fails due to an unknown variable, the request is returned in the * {@link UnusedEvaluationResult#getNewRequestSet() new request set} keyed by the now unknown variable. * @param oclFactory TODO * * @return a result that tells whether unusedness could be proven and which tells the potentially transformed * next {@link UnusedEvaluationRequestSet} which may be this unchanged object in case no request had a slot for * the variable inferred, or a new set which contains the re-organized and possibly cloned requests. */ public UnusedEvaluationResult setVariable(Variable variable, EObject value, OppositeEndFinder oppositeEndFinder, org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache tracebackCache, OCLFactory oclFactory) { UnusedEvaluationResult result; boolean changed = false; Map<Variable, Set<UnusedEvaluationRequest>> newRequestSet = new HashMap<Variable, Set<UnusedEvaluationRequest>>(); // enter variable value into those requests that have a slot for it for (Map.Entry<Variable, Set<UnusedEvaluationRequest>> e : requests.entrySet()) { UnusedEvaluationRequest[] newSet = new UnusedEvaluationRequest[e.getValue().size()]; int newSetIndex = 0; boolean setChanged = false; for (UnusedEvaluationRequest request : e.getValue()) { UnusedEvaluationRequest clonedRequest = request.setInferredVariableValue(variable, value, tracebackCache.getUnusedEvaluationRequestFactory()); newSet[newSetIndex++] = clonedRequest; if (clonedRequest != request) { changed = true; setChanged = true; } } if (!setChanged) { newRequestSet.put(e.getKey(), e.getValue()); } else { Set<UnusedEvaluationRequest> set = new HashSet<UnusedEvaluationRequest>(); for (int i=0; i<newSetIndex; i++) { set.add(newSet[i]); } newRequestSet.put(e.getKey(), set); } } // now re-evaluate those that previously failed because variable was unknown Collection<UnusedEvaluationRequest> triggered = newRequestSet.get(variable); if (triggered != null) { newRequestSet.remove(variable); changed = true; UnusedEvaluationRequestSet nextSet = new UnusedEvaluationRequestSet(newRequestSet); UnusedEvaluationResult preResult = evaluate(triggered, oppositeEndFinder, tracebackCache, oclFactory); result = new UnusedEvaluationResult(preResult.hasProvenUnused(), nextSet.merge(preResult.getNewRequestSet())); } else { result = new UnusedEvaluationResult(/* provedUnused */ false, changed ? new UnusedEvaluationRequestSet(newRequestSet) : this); } return result; } /** * Evaluates the <code>requestsToEvaluate</code>. If any of them returns <code>true</code> from its * {@link UnusedEvaluationRequest#evaluate(OppositeEndFinder, OCLFactory)} method, a result will be returned that returns * <code>true</code> from its {@link UnusedEvaluationResult#hasProvenUnused()} method. Otherwise, that result's method will * return <code>false</code>. If any request's evaluation failed for an unknown variable, the request will be added to a new * {@link UnusedEvaluationRequestSet} which is part of this method's result (see * {@link UnusedEvaluationResult#getNewRequestSet()}), keyed by the unknown {@link Variable}. * * @param requestsToEvaluate * may be <code>null</code> which will immediately cause the result to be a combination of <code>false</code> and a * new, empty {@link UnusedEvaluationRequestSet}. */ public static UnusedEvaluationResult evaluate(Collection<UnusedEvaluationRequest> requestsToEvaluate, OppositeEndFinder oppositeEndFinder, TracebackCache tracebackCache, OCLFactory oclFactory) { UnusedEvaluationResult result = null; Map<Variable, Set<UnusedEvaluationRequest>> newRequestSet = null; if (requestsToEvaluate != null) { newRequestSet = new HashMap<Variable, Set<UnusedEvaluationRequest>>(2); for (UnusedEvaluationRequest request : requestsToEvaluate) { Object evaluationResult = tracebackCache.getCachedEvaluationResult(request); if (evaluationResult == null) { evaluationResult = request.checkValuePresentForAllRequiredVariables(); if (evaluationResult == null) { // all inevitably required variables defined try { evaluationResult = request.evaluate(oppositeEndFinder, oclFactory); } catch (ValueNotFoundException vnfe) { evaluationResult = vnfe; } } tracebackCache.cacheEvaluationResult(request, evaluationResult); } if (evaluationResult instanceof VariableValueNotFoundInfo) { // re-add the request, but this time for the now unknown variable Variable unknownVariable = (Variable) ((VariableValueNotFoundInfo) evaluationResult).getVariableExp() .getReferredVariable(); Set<UnusedEvaluationRequest> newSet = newRequestSet.get(unknownVariable); if (newSet == null) { newSet = new HashSet<UnusedEvaluationRequest>(); newRequestSet.put(unknownVariable, newSet); } newSet.add(request); } else { // must be boolean, indicating result of successful evaluation if ((Boolean) evaluationResult) { result = new UnusedEvaluationResult(/* provedUnused */true, /* newRequestSet */null); break; } else { // else, simply don't add the resolved request anymore because it was unable to prove unused } } } } if (result == null) { result = new UnusedEvaluationResult(/* provedUnused */false, newRequestSet == null || newRequestSet.isEmpty() ? null : new UnusedEvaluationRequestSet(newRequestSet)); } return result; } /** * Produces a new instance of this class whose {@link #requests} map holds the combined sets of {@link UnusedEvaluationRequest}s * from this and the <code>other</code> set. * * @param other may be <code>null</code>, which makes the method return <code>this</code> unchanged */ public UnusedEvaluationRequestSet merge(UnusedEvaluationRequestSet other) { if (other == null || other.requests.isEmpty()) { if (requests.isEmpty()) { return null; } else { return this; } } else if (requests.isEmpty()) { return other; } else { Map<Variable, Set<UnusedEvaluationRequest>> newRequests = new HashMap<Variable, Set<UnusedEvaluationRequest>>( requests); for (Map.Entry<Variable, Set<UnusedEvaluationRequest>> e : other.requests.entrySet()) { if (!e.getValue().isEmpty()) { Set<UnusedEvaluationRequest> set = newRequests.get(e.getKey()); if (set == null || set.isEmpty()) { set = e.getValue(); } else { Set<UnusedEvaluationRequest> newSet = new HashSet<UnusedEvaluationRequest>(set.size() + 1); newSet.addAll(set); newSet.addAll(e.getValue()); set = newSet; } newRequests.put(e.getKey(), set); } } return new UnusedEvaluationRequestSet(newRequests); } } public String toString() { StringBuilder result = new StringBuilder("requests by unknown variable: ["); boolean firstVariable = true; for (Map.Entry<Variable, Set<UnusedEvaluationRequest>> e : requests.entrySet()) { if (!firstVariable) { result.append(", "); } else { firstVariable = false; } result.append(e.getKey().getName()); result.append(": ["); boolean first = true; for (UnusedEvaluationRequest request : e.getValue()) { if (!first) { result.append(","); } else { first = false; } result.append(request); } result.append("]"); } result.append("]"); return result.toString(); } }