blob: 2695e23637e14f5a7895772fbbbc7357ac65e3d8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2018 IBM Corporation, Zeligsoft Inc., 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:
* IBM - Initial API and implementation
* E.D.Willink - Refactoring to support extensibility and flexible error handling
* Zeligsoft - Bug 251349
* E.D.Willink - Bug 259818
* Radek Dvorak - Bug 261128
* Adolfo Sanchez-Barbudo Herrera (Open Canarias) - Bug 333032
*******************************************************************************/
package org.eclipse.ocl;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.helper.OCLHelper;
import org.eclipse.ocl.internal.OCLDebugOptions;
import org.eclipse.ocl.internal.OCLPlugin;
import org.eclipse.ocl.internal.evaluation.QueryImpl;
import org.eclipse.ocl.internal.helper.HelperUtil;
import org.eclipse.ocl.lpg.ProblemHandler;
import org.eclipse.ocl.parser.OCLAnalyzer;
import org.eclipse.ocl.parser.ValidationVisitor;
import org.eclipse.ocl.parser.backtracking.OCLBacktrackingLexer;
import org.eclipse.ocl.parser.backtracking.OCLBacktrackingParser;
import org.eclipse.ocl.types.OCLStandardLibrary;
import org.eclipse.ocl.util.OCLUtil;
import org.eclipse.ocl.util.ObjectUtil;
import org.eclipse.ocl.utilities.ExpressionInOCL;
/**
* <p>
* The Fa&ccedil;ade for parsing and evaluation of OCL documents and
* constraints. The <code>OCL</code> is initialized with an environment factory
* that supports the metamodel of the model to which the OCL constraints are to
* be applied.
* </p>
* <p>
* Since 1.2, the helper supplies {@linkplain #getProblems() diagnostics}
* indicating any problems encountered while parsing. The diagnostics pertain
* always to the most recently executed parse operation.
* </p>
* <p>
* See the {@link Environment} class for a description of the generic type
* parameters of this class.
* </p>
* <p>
* Providers of OCL bindings for metamodels may extend this class for the
* convenience of their clients, to specify the type parameter substitutions.
* </p>
*
* @author Christian W. Damus (cdamus)
*/
public class OCL<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> {
private final EnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> environmentFactory;
private final Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> rootEnvironment;
private EvaluationEnvironment<C, O, P, CLS, E> evalEnv;
private Map<CLS, ? extends Set<? extends E>> extentMap;
private List<CT> constraints = new java.util.ArrayList<CT>();
private Diagnostic problems;
private Diagnostic evaluationProblems;
private int parserRepairCount = 0;
private boolean traceParsing = OCLPlugin
.shouldTrace(OCLDebugOptions.PARSING);
private boolean traceEvaluation = OCLPlugin
.shouldTrace(OCLDebugOptions.EVALUATION);
/**
* Initializes me with an environment factory, which I will use to create a
* root environment.
*
* @param envFactory
* my environment factory
*/
protected OCL(
EnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> envFactory) {
this(envFactory, envFactory.createEnvironment());
}
/**
* Initializes me with a root environment.
*
* @param env
* my root environment
*/
protected OCL(Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env) {
this(env.getFactory(), env);
}
/**
* Initializes me with an environment factory and a resource in which my
* environment is to be persisted, or from which it may be loaded if the
* resource already has content.
*
* @param envFactory
* an environment factory
* @param resource
* a resource in which the root environment is persisted
*/
protected OCL(
EnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> envFactory,
Resource resource) {
this(envFactory, envFactory.loadEnvironment(resource));
}
/**
* Initializes me with my environment factory and root environment.
*
* @param envFactory
* my environment factory
* @param rootEnv
* my root environment
*/
protected OCL(
EnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> envFactory,
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> rootEnv) {
this.environmentFactory = envFactory;
this.rootEnvironment = rootEnv;
if (envFactory instanceof AbstractEnvironmentFactory<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>) {
AbstractEnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> abstractFactory = (AbstractEnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>) envFactory;
abstractFactory.setEvaluationTracingEnabled(traceEvaluation);
}
}
/**
* Creates a new <code>OCL</code> with an environment factory, which it will
* use to create a root environment.
*
* @param envFactory
* an environment factory
* @return the OCL fa&ccedil;ade
*/
public static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> OCL<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> newInstance(
EnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> envFactory) {
return new OCL<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>(envFactory);
}
/**
* Creates a new <code>OCL</code> on the specified root environment.
*
* @param env
* the root environment
* @return the OCL fa&ccedil;ade
*/
public static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> OCL<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> newInstance(
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env) {
return new OCL<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>(env);
}
/**
* Creates a new <code>OCL</code> with an environment factory and a resource
* in which its environment is to be persisted, or from which it may be
* loaded if the resource already has content.
*
* @param envFactory
* an environment factory
* @param resource
* a resource in which the root environment is persisted
* @return the OCL fa&ccedil;ade
*/
public static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> OCL<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> newInstance(
EnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> envFactory,
Resource resource) {
return new OCL<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>(
envFactory, resource);
}
/**
* Obtains the OCL parsing environment. Clients may manipulate this
* environment to support custom requirements, such as adding variables to
* it to define "global" values. For any variables that are added, bindings
* will have to provided, as well, in the
* {@linkplain #getEvaluationEnvironment() evaluation environment}.
*
* @return the parsing environment
*
* @see #getEvaluationEnvironment()
*/
public Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> getEnvironment() {
return rootEnvironment;
}
/**
* Obtains the OCL evaluation environment. Clients may manipulate this
* environment to support custom requirements, such as binding the values of
* "global" variables.
*
* @return the evaluation environment
*
* @see #getEnvironment()
*/
public EvaluationEnvironment<C, O, P, CLS, E> getEvaluationEnvironment() {
if (evalEnv == null) {
evalEnv = environmentFactory.createEvaluationEnvironment();
}
return evalEnv;
}
/**
* Obtains the extent map, if any, provided by the client to customize the
* evaluation of constraints.
*
* @return the client-provided custom extent map, or <code>null</code> if
* thie OCL is using the default dynamic extent map implementation
*/
public Map<CLS, ? extends Set<? extends E>> getExtentMap() {
return extentMap;
}
/**
* Assigns a custom extent map to define the extents of classes in
* evaluation of OCL constraints. This is only needed if the default dynamic
* extent-map implementation is not suitable.
*
* @param extentMap
* a custom extent map, or <code>null</code> to use the default
* dynamic extent map implementation
*/
public void setExtentMap(Map<CLS, ? extends Set<? extends E>> extentMap) {
this.extentMap = extentMap;
}
/**
* Return an analyzer configured ready to parse an input string.
*
* @param input the input to parse
* @return an analyzer
* @since 3.0
*/
public OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> createAnalyzer(String input) {
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer;
if (parserRepairCount != 0) {
OCLBacktrackingLexer lexer = new OCLBacktrackingLexer(
rootEnvironment, input.toCharArray());
OCLBacktrackingParser parser = new OCLBacktrackingParser(lexer);
parser.setDefaultRepairCount(parserRepairCount);
lexer.lexer(parser.getIPrsStream());
analyzer = rootEnvironment.getFactory().createOCLAnalyzer(parser);
} else {
analyzer = rootEnvironment.getFactory().createOCLAnalyzer(rootEnvironment, input);
}
return analyzer;
}
/**
* Parses an OCL document, returning the constraints parsed from it. This
* <code>OCL</code> instance remembers these constraints; they can be
* retrieved later via the {@link #getConstraints()} method.
*
* @param input
* an OCL document as either a string or a stream
* @return the constraints parsed from the document
*
* @throws ParserException
* on failure to parse, either because of a syntactic or
* semantic problem or because of an I/O failure
*
* @see #getConstraints()
*/
public List<CT> parse(OCLInput input)
throws ParserException {
String inputString = input.getContentAsString();
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer = createAnalyzer(inputString);
// clear out old diagnostics
ProblemHandler ph = OCLUtil.getAdapter(rootEnvironment,
ProblemHandler.class);
if (ph != null) {
ph.beginParse();
}
List<CT> result = new java.util.ArrayList<CT>();
analyzer.parseOCLDocument(result);
constraints.addAll(result);
List<EObject> resContents = rootEnvironment.getTypeResolver()
.getResource().getContents();
for (CT ct : result) {
EObject constraintEObject = (EObject) ct;
if (constraintEObject.eResource() == null) {
resContents.add(constraintEObject);
}
}
if (ph != null) {
ph.endParse();
try {
problems = OCLUtil.checkForErrors(ph);
} catch (ParserException e) {
problems = e.getDiagnostic();
throw e;
}
}
return result;
}
/**
* Obtains all of the constraints parsed hitherto by this OCL instance.
* These accumulate with every document that is parsed.
*
* @return the constraints that I have parsed
*
* @see #parse(OCLInput)
*/
public List<CT> getConstraints() {
return constraints;
}
/**
* Validates an OCL expression, which may have been loaded from some
* resource or constructed via the API (perhaps by translation from some
* other language).
*
* @param expression
* an expression to validate
*
* @throws SemanticException
* on detection of any well-formedness problem in the expression
*
* @see #validate(Object)
*/
public void validate(OCLExpression<C> expression)
throws SemanticException {
// clear out old diagnostics
ProblemHandler ph = OCLUtil.getAdapter(rootEnvironment,
ProblemHandler.class);
if (ph != null) {
ph.beginValidation();
}
expression.accept(ValidationVisitor.getInstance(rootEnvironment));
if (ph != null) {
ph.endValidation();
try {
OCLUtil.checkForErrors(ph);
} catch (SyntaxException e) {
// shouldn't actually be able to get this from validation
throw new SemanticException(e.getDiagnostic());
}
}
}
/**
* Validates an OCL constraint, which may have been loaded from some
* resource or constructed via the API (perhaps by translation from some
* other language).
*
* @param constraint
* a constraint to validate
*
* @throws SemanticException
* on detection of any well-formedness problem in the constraint
*/
public void validate(CT constraint)
throws SemanticException {
// clear out old diagnostics
ProblemHandler ph = OCLUtil.getAdapter(rootEnvironment,
ProblemHandler.class);
if (ph != null) {
ph.beginValidation();
}
ValidationVisitor.getInstance(rootEnvironment).visitConstraint(
constraint);
if (ph != null) {
ph.endValidation();
try {
OCLUtil.checkForErrors(ph);
} catch (SyntaxException e) {
// shouldn't actually be able to get this from validation
throw new SemanticException(e.getDiagnostic());
}
}
}
/**
* Evaluates a query expression on a context object (which is bound to the
* <tt>self</tt> variable). Clients should use the
* {@link #isInvalid(Object)} method to check whether the evaluation result
* is <tt>OclInvalid</tt>.
*
* @param context
* the context (self) object
* @param expression
* the OCL expression to evaluate
*
* @return the value of the expression, or <tt>OclInvalid</tt> if the
* evaluation fails for reasons other than a run-time exception
*
* @see #isInvalid(Object)
* @see #check(Object, Object)
*/
public Object evaluate(Object context, OCLExpression<C> expression) {
evaluationProblems = null;
// can determine a more appropriate context from the context
// variable of the expression, to account for stereotype constraints
context = HelperUtil.getConstraintContext(rootEnvironment, context,
expression);
EvaluationEnvironment<C, O, P, CLS, E> localEvalEnv = getEvaluationEnvironment();
localEvalEnv.add(Environment.SELF_VARIABLE_NAME, context);
Map<CLS, ? extends Set<? extends E>> extents = getExtentMap();
if (extents == null) {
// let the evaluation environment create one
extents = localEvalEnv.createExtentMap(context);
}
EvaluationVisitor<?, C, O, P, ?, ?, ?, ?, ?, CT, CLS, E> ev = environmentFactory
.createEvaluationVisitor(rootEnvironment, localEvalEnv, extents);
Object result;
try {
result = ev.visitExpression(expression);
} catch (EvaluationHaltedException e) {
evaluationProblems = e.getDiagnostic();
result = rootEnvironment.getOCLStandardLibrary().getInvalid();
} finally {
localEvalEnv.remove(Environment.SELF_VARIABLE_NAME);
}
return result;
}
/**
* Queries whether a value is the special <tt>invalid</tt> token. This is
* useful for determining whether the result of an expression evaluation is
* valid.
*
* @param value
* some OCL value
* @return <code>true</code> if it is the <tt>invalid</tt>;
* <code>false</code>, otherwise
*
* @see #evaluate(Object, OCLExpression)
*/
public boolean isInvalid(Object value) {
return getEnvironment().getOCLStandardLibrary().getInvalid() == value;
}
/**
* Checks whether a constraint is satisfied by an object. If the constraint
* is an invariant constraint, then no additional variable bindings are
* required. If it is an operation precondition or postcondition, however,
* then the appropriate parameter variables and (in the postcondition case)
* result variable should be bound in the evaluation environment.
*
* @param context
* the <tt>self</tt> object of the constraint
* @param constraint
* the constraint to check
*
* @return whether the context object satisfies the constraint
*
* @see #check(Object, OCLExpression)
* @see #evaluate(Object, OCLExpression)
*/
public boolean check(Object context, CT constraint) {
ExpressionInOCL<C, PM> specification = rootEnvironment
.getUMLReflection().getSpecification(constraint);
return check(context, specification.getBodyExpression());
}
/**
* Checks whether a constraint, specified simply as an OCL expression, is
* satisfied by an object. If the constraint is an invariant constraint,
* then no additional variable bindings are required. If it is an operation
* precondition or postcondition, however, then the appropriate parameter
* variables and (in the postcondition case) result variable should be bound
* in the evaluation environment.
*
* @param context
* the <tt>self</tt> object of the constraint
* @param constraint
* the constraint to check, which must be a boolean-valued
* expression
*
* @return whether the context object satisfies the constraint
*
* @see #check(Object, Object)
* @see #evaluate(Object, OCLExpression)
*
* @throws IllegalArgumentException
* if the constraint expression is not boolean-valued
*/
public boolean check(Object context, OCLExpression<C> constraint) {
OCLStandardLibrary<C> stdlib = getEnvironment().getOCLStandardLibrary();
if (constraint.getType() != stdlib.getBoolean()) {
throw new IllegalArgumentException("constraint is not boolean"); //$NON-NLS-1$
}
Object result = evaluate(context, constraint);
return Boolean.TRUE.equals(result);
}
/**
* Creates a new {@link OCLHelper} instance for convenient parsing of
* embedded constraints and query expressions in this environment. The
* helper is particulary useful for parsing constraints embedded in the
* model, in which case the context of a constraint is determined by its
* placement and the textual context declarations are unnecessary.
*
* @return a new helper object
*/
public OCLHelper<C, O, P, CT> createOCLHelper() {
return HelperUtil.createOCLHelper(this);
}
/**
* Creates a new {@link Query} encapsulating a query expression with the
* current environment and extent map. This is convenient for repeated
* evaluation of expressions and for filtering/transforming objects using a
* query or constraint expression.
* <p>
* Every query maintains its own evaluation environment, which enables
* concurrent evaluation (where this may be safe in an EMF-based model) and
* different bindings for client-supplied "global" variables.
* </p>
*
* @param query
* the OCL query expression, which may be interpreted as a
* constraint if it is boolean-valued
*
* @return the new query object
*
* @see #createQuery(Object)
*/
public Query<C, CLS, E> createQuery(OCLExpression<C> query) {
return new QueryImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>(
rootEnvironment, query, extentMap);
}
/**
* Creates a new {@link Query} encapsulating a constraint with the current
* environment and extent map. This is convenient for repeated evaluation of
* constraints and for filtering objects using the constraint expression.
* <p>
* Every query maintains its own evaluation environment, which enables
* concurrent evaluation (where this may be safe in an EMF-based model) and
* different bindings for client-supplied "global" variables.
* </p>
*
* @param constraint
* the OCL constraint
*
* @return the new query object
*
* @see #createQuery(OCLExpression)
*/
public Query<C, CLS, E> createQuery(CT constraint) {
return new QueryImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>(
rootEnvironment, rootEnvironment.getUMLReflection()
.getSpecification(constraint).getBodyExpression(), extentMap);
}
/**
* Queries whether tracing of parsingis enabled. Tracing logs the progress
* of parsing to the console, which may be of use in diagnosing problems.
* <p>
* In an Eclipse environment, tracing is also enabled by turning on the
* <tt>org.eclipse.ocl/debug/parsing</tt> debug option.
* </p>
*
* @return whether parse tracing is enabled
*
* @see #setParseTracingEnabled(boolean)
*/
public boolean isParseTracingEnabled() {
return traceParsing;
}
/**
* Sets whether tracing of parsing is enabled. Tracing logs the progress of
* parsing to the console, which may be of use in diagnosing problems.
* <p>
* In an Eclipse environment, tracing is also enabled by turning on the
* <tt>org.eclipse.ocl/debug/parsing</tt> debug option.
* </p>
*
* param b whether parsing tracing is enabled
*
* @see #isParseTracingEnabled()
*/
public void setParseTracingEnabled(boolean b) {
traceParsing = b;
}
/**
* <p>
* Queries the number of repairs to be made by the parser.
* </p>
* <p>
* The default zero value selects use of the deterministic parser, which
* terminates after one serious syntax error is detected.
* </p>
* <p>
* A non-zero value selects the backtracking parser. The backtracking parser
* may be about three times slower.
* </p>
*
* @return the number of repairs to be attempted
*
* @see #setParserRepairCount(int)
* @since 1.3
*/
public int getParserRepairCount() {
return parserRepairCount;
}
/**
* <p>
* Sets the number of repairs to be made by the parser.
* </p>
* <p>
* The default zero value selects use of the deterministic parser, which
* terminates after one serious syntax error is detected.
* </p>
* <p>
* A non-zero value selects the backtracking parser. The backtracking parser
* may be about three times slower.
* </p>
*
* @param parserRepairCount
* whether evaluation tracing is enabled
*
* @throws IllegalArgumentException
* if the <tt>parserRepairCount</tt> is negative
*
* @see #getParserRepairCount()
* @since 1.3
*/
public void setParserRepairCount(int parserRepairCount) {
if (parserRepairCount < 0) {
throw new IllegalArgumentException("negative repair count"); //$NON-NLS-1$
}
this.parserRepairCount = parserRepairCount;
}
/**
* Queries whether tracing of evaluation is enabled. Tracing logs the
* progress of evaluation to the console, which may be of use in diagnosing
* problems.
* <p>
* In an Eclipse environment, tracing is also enabled by turning on the
* <tt>org.eclipse.ocl/debug/evaluation</tt> debug option.
* </p>
*
* @return whether evaluation tracing is enabled
*
* @see #setEvaluationTracingEnabled(boolean)
*/
public boolean isEvaluationTracingEnabled() {
return traceEvaluation;
}
/**
* Sets whether tracing of evaluation is enabled. Tracing logs the progress
* of parsing to the console, which may be of use in diagnosing problems.
* <p>
* In an Eclipse environment, tracing is also enabled by turning on the
* <tt>org.eclipse.ocl/debug/evaluation</tt> debug option.
* </p>
*
* @param b
* whether evaluation tracing is enabled
*
* @see #isEvaluationTracingEnabled()
*/
public void setEvaluationTracingEnabled(boolean b) {
traceEvaluation = b;
if (environmentFactory instanceof AbstractEnvironmentFactory<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>) {
AbstractEnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> abstractFactory = (AbstractEnvironmentFactory<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>) environmentFactory;
abstractFactory.setEvaluationTracingEnabled(traceEvaluation);
}
}
/**
* Obtains problems, if any, found in parsing the last OCL constraint or
* query expression.
*
* @return parsing problems or <code>null</code> if all was OK
*
* @since 1.2
*/
public Diagnostic getProblems() {
return problems;
}
/**
* Obtains problems, if any, occurred during evaluation of the last OCL
* constraint or query expression.
*
* @return evaluation problems or <code>null</code> if all was OK
*
* @since 1.3
*/
public Diagnostic getEvaluationProblems() {
return evaluationProblems;
}
/**
* Disposes any objects that I have created while I have been in use. This
* includes disposing of any {@link #getConstraints() constraints} that I
* have parsed and {@linkplain Environment.Internal#dispose() disposing} of
* my environment.
*
* @since 1.2
*/
public void dispose() {
// dispose of constraints by clearing their adapters
for (CT constraint : getConstraints()) {
EObject eObject = (EObject) constraint;
if (eObject.eResource() == null) {
ObjectUtil.dispose(constraint);
}
}
// forget the constraints
getConstraints().clear();
// dispose of my environment
if (getEnvironment() instanceof Environment.Internal<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>) {
Environment.Internal<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> env = (Environment.Internal<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>) getEnvironment();
env.dispose();
}
}
}