blob: ef2e2fac0bf72b477c14e27ada4ee4ed62f272f8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2018 The University of York.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Dimitrios Kolovos - initial API and implementation
* Sina Madani - refactoring
******************************************************************************/
package org.eclipse.epsilon.evl.dom;
import java.util.*;
import org.eclipse.epsilon.common.module.IModule;
import org.eclipse.epsilon.common.parse.AST;
import org.eclipse.epsilon.common.util.AstUtil;
import org.eclipse.epsilon.eol.dom.ExecutableBlock;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.execute.context.FrameType;
import org.eclipse.epsilon.eol.execute.context.Variable;
import org.eclipse.epsilon.erl.dom.NamedRule;
import org.eclipse.epsilon.erl.execute.context.IErlContext;
import org.eclipse.epsilon.evl.execute.FixInstance;
import org.eclipse.epsilon.evl.execute.UnsatisfiedConstraint;
import org.eclipse.epsilon.evl.execute.context.IEvlContext;
import org.eclipse.epsilon.evl.execute.operations.SatisfiesOperation;
import org.eclipse.epsilon.evl.parse.EvlParser;
public class Constraint extends NamedRule {
protected boolean isCritique = false;
protected List<Fix> fixes;
protected ConstraintContext constraintContext;
protected ExecutableBlock<Boolean> guardBlock;
protected ExecutableBlock<Boolean> checkBlock;
protected ExecutableBlock<String> messageBlock;
/**
* This will be set to <code>true</code> by {@link SatisfiesOperation}.
* @since 1.6
*/
protected boolean isDependedOn = false;
@Override
@SuppressWarnings("unchecked")
public void build(AST cst, IModule module) {
super.build(cst, module);
if (cst.getType() == EvlParser.CRITIQUE) {
isCritique = true;
}
this.guardBlock = (ExecutableBlock<Boolean>) module.createAst(AstUtil.getChild(cst, EvlParser.GUARD), this);
this.checkBlock = (ExecutableBlock<Boolean>) module.createAst(AstUtil.getChild(cst,EvlParser.CHECK), this);
this.messageBlock = (ExecutableBlock<String>) module.createAst(AstUtil.getChild(cst, EvlParser.MESSAGE), this);
List<AST> fixASTs = AstUtil.getChildren(cst, EvlParser.FIX);
fixes = new ArrayList<>(fixASTs.size());
for (AST fixAst : fixASTs) {
fixes.add((Fix) module.createAst(fixAst, this));
}
}
public boolean isInfo() {
return isCritique() && hasAnnotation("info");
}
public boolean isLazy(IEvlContext context) throws EolRuntimeException {
return getBooleanAnnotationValue("lazy", context);
}
public boolean shouldBeChecked(Object modelElement, IEvlContext context) throws EolRuntimeException {
return !isLazy(context) && appliesTo(modelElement, context);
}
@SuppressWarnings("unchecked")
@Override
public final Optional<UnsatisfiedConstraint> execute(Object self, IErlContext context) throws EolRuntimeException {
return (Optional<UnsatisfiedConstraint>) super.execute(self, context);
}
/**
* @since 1.6
*/
@Override
public final Optional<UnsatisfiedConstraint> executeImpl(Object modelElement, IErlContext context_) throws EolRuntimeException {
IEvlContext context = (IEvlContext) context_;
if (shouldBeChecked(modelElement, context)) {
return check(modelElement, context);
}
return Optional.empty();
}
// FIXME : Currently examines only the local guard
public boolean appliesTo(Object object, IEvlContext context) throws EolRuntimeException {
if (guardBlock != null)
return guardBlock.execute(context, Variable.createReadOnlyVariable("self", object));
return true;
}
/**
*
* @param self
* @param context
* @param result
* @return
* @throws EolRuntimeException
* @since 1.6
*/
public boolean optimisedCheck(Object self, IEvlContext context, boolean result) throws EolRuntimeException {
return postprocessCheck(self, context, preprocessCheck(self, context), result);
}
/**
*
* @param self
* @param context
* @return
* @throws EolRuntimeException
* @since 1.6
*/
public Optional<UnsatisfiedConstraint> check(Object self, IEvlContext context) throws EolRuntimeException {
UnsatisfiedConstraint unsatisfiedConstraint = preprocessCheck(self, context);
boolean result;
// Look for a result in the trace first if this constraint is a dependency, otherwise run the check block
if (isDependedOn && context.getConstraintTrace().isChecked(this, self)) {
result = context.getConstraintTrace().isSatisfied(this, self);
}
else {
result = executeCheckBlock(self, context);
if (!context.isOptimizeConstraintTrace()) {
context.getConstraintTrace().addChecked(this, self, result);
}
}
return !postprocessCheck(self, context, unsatisfiedConstraint, result)
? Optional.of(unsatisfiedConstraint) : Optional.empty();
}
protected UnsatisfiedConstraint preprocessCheck(Object self, IEvlContext context) {
UnsatisfiedConstraint unsatisfiedConstraint = new UnsatisfiedConstraint();
context.getFrameStack()
.enterLocal(FrameType.UNPROTECTED, checkBlock.getBody())
.put(
Variable.createReadOnlyVariable("self", self),
Variable.createReadOnlyVariable("extras", unsatisfiedConstraint.getExtras())
);
return unsatisfiedConstraint;
}
/**
*
* @param self
* @param context
* @return
* @throws EolRuntimeException
* @since 1.6
*/
protected boolean executeCheckBlock(Object self, IEvlContext context) throws EolRuntimeException {
return checkBlock.execute(context, false);
}
protected boolean postprocessCheck(Object self, IEvlContext context, UnsatisfiedConstraint unsatisfiedConstraint, boolean result) throws EolRuntimeException {
if (!result) {
unsatisfiedConstraint.setInstance(self);
unsatisfiedConstraint.setConstraint(this);
String messageResult = messageBlock != null ?
messageBlock.execute(context, false) :
"Invariant "+this.getName()+" failed for "+context.getPrettyPrinterManager().toString(self);
unsatisfiedConstraint.setMessage(messageResult);
context.getUnsatisfiedConstraints().add(unsatisfiedConstraint);
Collection<FixInstance> unsatisfiedConstraintFixes = unsatisfiedConstraint.getFixes();
for (Fix fix : fixes) {
if (!fix.appliesTo(self, context)) continue;
FixInstance fixInstance = new FixInstance(context, fix);
fixInstance.setSelf(self);
unsatisfiedConstraintFixes.add(fixInstance);
}
}
// Don't dispose the frame we leave if unsatisfied because it may be needed for fix parts,
// otherwise leaveLocal if constraint is satisfied or there are no possible further uses for it.
context.getFrameStack().leaveLocal(checkBlock.getBody(), result || (fixes.isEmpty() && messageBlock == null));
return result;
}
/**
* Checks whether this constraint's guard block has dependencies on other constraint(s).
*
* @return <code>true</code> if the guard block exists and has a satisfies call.
* @since 1.6
*/
public boolean guardBlockUsesSatisfies() {
return guardBlock != null && guardBlock.getText().contains("satisfies");
}
public ConstraintContext getConstraintContext() {
return constraintContext;
}
public void setConstraintContext(ConstraintContext constraintContext) {
setParent(this.constraintContext = constraintContext);
}
public boolean isCritique() {
return isCritique;
}
public void setCritique(boolean isCritique) {
this.isCritique = isCritique;
}
/**
* @return Whether this constraint is the target of a dependency.
* @since 1.6
*/
public boolean isDependedOn() {
return isDependedOn;
}
/**
* Used to flag this constraint as the target of a dependency.
* @since 1.6
*/
public void setAsDependency() {
this.isDependedOn = true;
}
/**
* @since 1.6
*/
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), constraintContext, isCritique);
}
/**
* @since 1.6
*/
@Override
public boolean equals(Object other) {
if (!super.equals(other))
return false;
Constraint constraint = (Constraint) other;
return
Objects.equals(this.constraintContext, constraint.constraintContext) &&
Objects.equals(this.isCritique, constraint.isCritique);
}
}