| /******************************************************************************* |
| * Copyright (c) 2009, 2011 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(); |
| } |
| } |