blob: a2deb9a2203f2816f1bc9c5c2d39762bf8c3fa8e [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2007,2011 IBM Corporation 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:
* 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
*
* </copyright>
*
* $Id: OCLBase.java,v 1.7 2011/04/20 19:02:47 ewillink Exp $
*/
package org.eclipse.ocl.examples.pivot;
import java.util.List;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.ocl.examples.domain.elements.DomainStandardLibrary;
import org.eclipse.ocl.examples.domain.evaluation.EvaluationHaltedException;
import org.eclipse.ocl.examples.domain.evaluation.InvalidEvaluationException;
import org.eclipse.ocl.examples.domain.evaluation.DomainModelManager;
import org.eclipse.ocl.examples.domain.values.Value;
import org.eclipse.ocl.examples.domain.values.ValueFactory;
import org.eclipse.ocl.examples.pivot.evaluation.EvaluationEnvironment;
import org.eclipse.ocl.examples.pivot.evaluation.EvaluationVisitor;
import org.eclipse.ocl.examples.pivot.manager.MetaModelManager;
import org.eclipse.ocl.examples.pivot.util.PivotPlugin;
import org.eclipse.ocl.examples.pivot.utilities.QueryBaseImpl;
/**
* <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 abstract class OCLBase {
private final EnvironmentFactory environmentFactory;
private final Environment rootEnvironment;
private EvaluationEnvironment evalEnv;
private DomainModelManager modelManager;
private List<Constraint> constraints = new java.util.ArrayList<Constraint>();
private Diagnostic problems;
private Diagnostic evaluationProblems;
private int parserRepairCount = 0;
private boolean traceParsing = PivotPlugin
.shouldTrace(OCLDebugOptions.PARSING);
private boolean traceEvaluation = PivotPlugin
.shouldTrace(OCLDebugOptions.EVALUATION);
/**
* Initializes me with an environment factory, which I will use to create a
* root environment.
*
* @param envFactory
* my environment factory
*/
protected OCLBase(
EnvironmentFactory envFactory) {
this(envFactory, envFactory.createEnvironment());
}
/**
* Initializes me with a root environment.
*
* @param env
* my root environment
*/
protected OCLBase(Environment 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 OCLBase(
EnvironmentFactory 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 OCLBase(
EnvironmentFactory envFactory,
Environment rootEnv) {
this.environmentFactory = envFactory;
this.rootEnvironment = rootEnv;
if (envFactory instanceof AbstractEnvironmentFactory) {
AbstractEnvironmentFactory abstractFactory = (AbstractEnvironmentFactory) 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 OCLBase newInstance(
EnvironmentFactory envFactory) {
return new OCLBase(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 OCLBase newInstance(
Environment env) {
return new OCLBase(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 OCLBase newInstance(
EnvironmentFactory envFactory,
Resource resource) {
return new OCLBase(
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 getEnvironment() {
return rootEnvironment;
}
public EnvironmentFactory getEnvironmentFactory() {
return environmentFactory;
}
/**
* 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 getEvaluationEnvironment() {
if (evalEnv == null) {
evalEnv = environmentFactory.createEvaluationEnvironment();
}
return evalEnv;
}
/**
* Obtains the model manager, if any, provided by the client to customize the
* evaluation of constraints.
*
* @return the client-provided custom model manager, or <code>null</code> if
* thie OCL is using the default dynamic extent map implementation
*/
public DomainModelManager getModelManager() {
return modelManager;
}
/**
* 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 modelManager
* a custom extent map, or <code>null</code> to use the default
* dynamic extent map implementation
*/
public void setModelManager(DomainModelManager modelManager) {
this.modelManager = modelManager;
}
/**
* 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<Constraint> 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 expression) throws SemanticException {
throw new UnsupportedOperationException(getClass().getName() + ".validate");
// 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(Constraint constraint) throws SemanticException {
throw new UnsupportedOperationException(getClass().getName() + ".validate");
// 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 org.eclipse.ocl.examples.domain.values.Value evaluate(Object context, ExpressionInOcl 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 localEvalEnv = getEvaluationEnvironment();
ValueFactory valueFactory = localEvalEnv.getValueFactory();
Value value = valueFactory.valueOf(context);
localEvalEnv.add(expression.getContextVariable(), value);
// if ((value != null) && !value.isUndefined()) {
// expression.getContextVariable().setValue(value);
// }
DomainModelManager extents = getModelManager();
if (extents == null) {
// let the evaluation environment create one
extents = localEvalEnv.createModelManager(context);
}
EvaluationVisitor ev = environmentFactory
.createEvaluationVisitor(rootEnvironment, localEvalEnv, extents);
org.eclipse.ocl.examples.domain.values.Value result;
try {
result = expression.accept(ev);
} catch (EvaluationHaltedException e) {
evaluationProblems = e.getDiagnostic();
throw e;
} finally {
localEvalEnv.remove(expression.getContextVariable());
}
if (result == null) {
return localEvalEnv.throwInvalidEvaluation("Java-Null value");
}
if (result.isInvalid()) {
return localEvalEnv.throwInvalidEvaluation("Invalid Value");
}
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().getInvalidValue() == 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, Constraint constraint) {
ExpressionInOcl specification = (ExpressionInOcl) constraint.getSpecification();
return check(context, specification);
}
/**
* 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, ExpressionInOcl specification) {
DomainStandardLibrary stdlib = getEnvironment().getOCLStandardLibrary();
if (specification.getBodyExpression().getType() != stdlib.getBooleanType()) {
throw new IllegalArgumentException("constraint is not boolean"); //$NON-NLS-1$
}
try {
Value result = evaluate(context, specification);
return result.isTrue();
} catch (InvalidEvaluationException e) {
return false;
}
}
/**
* 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 createOCLHelper() {
// return HelperUtil.createOCLHelper(this);
// }
/**
* Creates a new {@link QueryBase} 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 QueryBase createQuery(ExpressionInOcl specification) {
return new QueryBaseImpl(rootEnvironment, specification, modelManager);
}
/**
* Creates a new {@link QueryBase} 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 QueryBase createQuery(Constraint constraint) {
return new QueryBaseImpl(
rootEnvironment, (ExpressionInOcl) constraint.getSpecification(), modelManager);
}
/**
* 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)
*/
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()
*/
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 abstractFactory = (AbstractEnvironmentFactory) 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
*/
public Diagnostic getProblems() {
return problems;
}
public MetaModelManager getMetaModelManager() {
return rootEnvironment.getMetaModelManager();
}
public ValueFactory getValueFactory() {
return getMetaModelManager().getValueFactory();
}
/**
* 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
*/
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.
*/
public void dispose() {
// dispose of constraints by clearing their adapters
for (Constraint 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();
}
getMetaModelManager().dispose();
}
}