blob: 6da8e792e5432db98446de3ead40dd30eaa20f5f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2018 IBM Corporation 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
*******************************************************************************/
package org.eclipse.ocl.internal.helper;
import java.io.LineNumberReader;
import java.io.StringReader;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.OCL;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.Variable;
import org.eclipse.ocl.helper.OCLHelper;
import org.eclipse.ocl.internal.OCLPlugin;
import org.eclipse.ocl.lpg.ProblemHandler;
import org.eclipse.ocl.options.ParsingOptions;
import org.eclipse.ocl.parser.OCLAnalyzer;
import org.eclipse.ocl.parser.ValidationVisitor;
import org.eclipse.ocl.util.OCLUtil;
import org.eclipse.ocl.util.ObjectUtil;
import org.eclipse.ocl.utilities.ASTNode;
import org.eclipse.ocl.utilities.ExpressionInOCL;
/**
* Utility class in support of the implementation of the {@link OCLHelper}
* API, also responsible for the creation of {@link OCLHelper}s.
*
* @author Yasser Lulu
* @author Christian W. Damus (cdamus)
*/
public class HelperUtil {
static final int NONE = -1;
static final String OCL_COMMENT = "--"; //$NON-NLS-1$
static final String PATH_DELIMETER = "(=> "; //$NON-NLS-1$
static final String PACKAGE = "package"; //$NON-NLS-1$
static final String COLON = ":"; //$NON-NLS-1$
static final String DOUBLE_COLON = "::"; //$NON-NLS-1$
static final String DOT = "."; //$NON-NLS-1$
static final String ARROW = "->"; //$NON-NLS-1$
static final String CARET = "^"; //$NON-NLS-1$
static final String DOUBLE_CARET = "^^"; //$NON-NLS-1$
static final String EMPTY = ""; //$NON-NLS-1$
static final String HTTP = "http://"; //$NON-NLS-1$
/** Not instantiable by clients. */
private HelperUtil() {
super();
}
/**
* Creates an {@link OCLHelper} for the specified EMF metamodel. The
* factory creates OCL environments (with the packages, classifiers, states,
* etc.) from the instances of the metaclasses that mimic OCL/UML
* classifiers.
* <p>
* The new helper validates the OCL expressions that it parses.
* </p>
*
* @param ocl the metamodel-specific OCL environment
*
* @return the new OCL helper
*/
public static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
OCLHelper<C, O, P, CT>
createOCLHelper(OCL<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> ocl) {
return new OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>(ocl);
}
/**
* convenience method for serviceability support tracing exceptions thrown
* @param exception the exception to be thrown
* @param clazz the metaclass of the java object that owns the method
* @param methodName the method that threw the exception
*/
static void throwException(RuntimeException exception, Class<?> clazz,
String methodName) {
OCLPlugin.throwing(clazz, methodName, exception);
throw exception;
}
/**
* convenience method for serviceability support tracing exceptions caught
* @param exception the exception to be caught
* @param clazz the metaclass of the java object that owns the method
* @param methodName the method that caught the exception
*/
static void catchException(Exception exception, Class<?> clazz,
String methodName) {
OCLPlugin.catching(clazz, methodName, exception);
}
/**
* combines several physical non-commented lines into one logical line by
* removing white spaces and embedded comments
* @param txt the string we got from client that contains the ocl expression
* @return String the non-commented lines of the input concatenated as one
* @throws Exception if while traversing the string something went wrong
* mainly to account for (IOException)
*/
static String getLogicalLine(String txt) throws Exception {
LineNumberReader reader = new LineNumberReader(new StringReader(txt
.trim()));
String logicalLine = EMPTY;
int embeddedCommnetIndex = HelperUtil.NONE;
String line = reader.readLine();
while (line != null) {
line = line.trim();
if (line.startsWith(HelperUtil.OCL_COMMENT) == false) {
// safe to search for index of "--" which consists of BMP code points
embeddedCommnetIndex = line.indexOf(HelperUtil.OCL_COMMENT);
if (embeddedCommnetIndex != HelperUtil.NONE) {
line = line.substring(0, embeddedCommnetIndex);
line = line.trim();
}
logicalLine = logicalLine + line + ' ';
}
line = reader.readLine();
}
return logicalLine;
}
static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
OCLExpression<C> parseQuery(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper,
String expression,
boolean validate,
boolean trace) throws ParserException {
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env = helper.getEnvironment();
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer =
createAnalyzer(env, "inv:", expression, trace); //$NON-NLS-1$
CT constraint = analyzer.parseInvOrDefCS();
checkForErrors(helper);
ExpressionInOCL<C, PM> spec = env.getUMLReflection().getSpecification(constraint);
OCLExpression<C> result = spec.getBodyExpression();
// this is not a constraint
env.getUMLReflection().setSpecification(constraint, null);
spec.setBodyExpression(null);
if (validate) {
validate(env, result);
}
finishAnalyzing(helper);
// re-persist the "self" variable that we temporarily stole away from
// the environment, in the ExpressionInOCL. In a query expression,
// there won't be other variables (result, parameters) to worry about
persist(env, spec.getContextVariable());
// persist the expression
persist(env, result);
// dispose the remainder of the constraint
ObjectUtil.dispose(constraint);
return result;
}
static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
CT parseInvariant(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper,
String expression,
boolean validate,
boolean trace) throws ParserException {
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env = helper.getEnvironment();
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer =
createAnalyzer(env, "inv:", expression, trace); //$NON-NLS-1$
CT result = analyzer.parseInvOrDefCS();
checkForErrors(helper);
if (validate) {
validate(env, result);
}
finishAnalyzing(helper);
persist(helper, result);
return result;
}
static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
CT parsePrecondition(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper,
String expression,
boolean validate,
boolean trace) throws ParserException {
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env = helper.getEnvironment();
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer =
createAnalyzer(env, "pre:", expression, trace); //$NON-NLS-1$
CT result = analyzer.parsePrePostOrBodyDeclCS();
checkForErrors(helper);
if (validate) {
validate(env, result);
}
finishAnalyzing(helper);
persist(helper, result);
return result;
}
static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
CT parsePostcondition(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper,
String expression,
boolean validate,
boolean trace) throws ParserException {
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env = helper.getEnvironment();
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer =
createAnalyzer(env, "post:", expression, trace); //$NON-NLS-1$
CT result = analyzer.parsePrePostOrBodyDeclCS();
checkForErrors(helper);
if (validate) {
validate(env, result);
}
finishAnalyzing(helper);
persist(helper, result);
return result;
}
static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
CT parseBodyCondition(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper,
String expression,
boolean validate,
boolean trace) throws ParserException {
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env = helper.getEnvironment();
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer =
createAnalyzer(env, "body:", expression, trace); //$NON-NLS-1$
CT result = analyzer.parsePrePostOrBodyDeclCS();
checkForErrors(helper);
if (validate) {
validate(env, result);
}
finishAnalyzing(helper);
persist(helper, result);
return result;
}
static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
CT parseInitialValueExpression(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper,
String expression,
boolean validate,
boolean trace) throws ParserException {
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env = helper.getEnvironment();
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer =
createAnalyzer(env, "init:", expression, trace); //$NON-NLS-1$
CT result = analyzer.parseInitOrDerValueCS();
checkForErrors(helper);
if (validate) {
validate(env, result);
}
finishAnalyzing(helper);
persist(helper, result);
return result;
}
static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
CT parseDerivedValueExpression(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper,
String expression,
boolean validate,
boolean trace) throws ParserException {
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env = helper.getEnvironment();
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer =
createAnalyzer(env, "derive:", expression, trace); //$NON-NLS-1$
CT result = analyzer.parseInitOrDerValueCS();
checkForErrors(helper);
if (validate) {
validate(env, result);
}
finishAnalyzing(helper);
persist(helper, result);
return result;
}
static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
CT parseDefExpression(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper,
String defExpression,
boolean validate,
boolean trace) throws ParserException {
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env = helper.getEnvironment();
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> analyzer =
createAnalyzer(env, "def:", defExpression, trace); //$NON-NLS-1$
// we want to make the defined feature available. This is OK, since
// the constraint will be discarded anyway
analyzer.getEnvironment().setOption(
ParsingOptions.DEFINITION_CONSTRAINS_FEATURE, true);
CT result = analyzer.parseInvOrDefCS();
checkForErrors(helper);
if (validate) {
validate(env, result);
}
finishAnalyzing(helper);
persist(helper, result);
return result;
}
/**
* Initializes an analyzer on the specified <code>text</code> and environment
* factory.
*
* @param prefix the constraint prefix (e.g., <code>"inv:"</code> or
* <code>"pre:"</code>). The prefix must not contain newlines
* @param environmentFactory the analyzer's environment factory
* @param text the OCL constraint text
* @param trace whether to trace the parsing
*
* @return the analyzer
*/
private static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
createAnalyzer(
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env,
String prefix, String text,
boolean trace) {
// we prefix the constraint with "inv:", "pre:", "def:", etc. which the
// user cannot see, so we want error reporting to be relative
// to line 0, not line 1. Also, clear any old diagnostics
ProblemHandler ph = OCLUtil.getAdapter(env, ProblemHandler.class);
if (ph != null) {
ph.setErrorReportLineOffset(-1);
ph.beginParse();
}
OCLAnalyzer<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
result = env.getFactory().createOCLAnalyzer(env, prefix + '\n' + text);
// offset the character position by the length of the extra text
result.setCharacterOffset(-(prefix.length() + 1)); // one for the newline
result.setTraceFlag(trace);
return result;
}
/**
* Completes an environment's parsing session.
*
* @param helper the helper implementation
*/
private static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
void finishAnalyzing(OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper)
throws ParserException {
ProblemHandler ph = OCLUtil.getAdapter(helper.getEnvironment(),
ProblemHandler.class);
if (ph != null) {
ph.endParse();
}
checkForErrors(helper);
}
private static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
void validate(
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env,
OCLExpression<C> expression) throws ParserException {
expression.accept(ValidationVisitor.getInstance(env));
}
private static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
void validate(
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env,
CT constraint) throws ParserException {
ValidationVisitor.getInstance(env).visitConstraint(constraint);
}
private static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
void persist(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper,
CT constraint) {
EObject constraintEObject = (EObject) constraint;
if (constraintEObject.eResource() == null) {
helper.getEnvironment().getTypeResolver().getResource().getContents().add(
constraintEObject);
}
helper.getOCL().getConstraints().add(constraint);
}
private static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
void persist(
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env,
ASTNode astNode) {
if (astNode.eResource() == null) {
env.getTypeResolver().getResource().getContents().add(astNode);
}
}
public static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
Object getConstraintContext(
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env,
Object element,
OCLExpression<C> expr) {
Object result = element;
if (expr.eContainer() instanceof ExpressionInOCL<?, ?>) {
@SuppressWarnings("unchecked")
ExpressionInOCL<C, PM> specification =
(ExpressionInOCL<C, PM>) expr.eContainer();
Variable<C, PM> contextVariable = specification.getContextVariable();
if (contextVariable != null) {
C contextClassifier = contextVariable.getType();
if ((contextClassifier != null) && env.getUMLReflection().isStereotype(
contextClassifier)) {
Object application = env.getUMLReflection().getStereotypeApplication(
element, contextClassifier);
if (application != null) {
result = application;
}
}
}
}
return result;
}
private static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
void checkForErrors(
OCLHelperImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> helper)
throws ParserException {
try {
helper.setProblems(OCLUtil.checkForErrors(helper.getEnvironment()));
} catch (ParserException e) {
helper.setProblems(e.getDiagnostic());
throw e;
}
}
}