blob: b3239fe6974fe1d708648b34771251c23c1b515b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 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
* Zeligsoft - Bugs 2488692, 253252, 259630
* E.D.Willink - Bug 295166
*******************************************************************************/
package org.eclipse.ocl.uml.tests;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.EnvironmentFactory;
import org.eclipse.ocl.EvaluationEnvironment;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.lpg.ProblemHandler;
import org.eclipse.ocl.options.Customizable;
import org.eclipse.ocl.options.EvaluationOptions;
import org.eclipse.ocl.options.Option;
import org.eclipse.ocl.options.ParsingOptions;
import org.eclipse.ocl.options.ProblemOption;
import org.eclipse.ocl.uml.OCL;
import org.eclipse.ocl.uml.UMLEnvironment;
import org.eclipse.ocl.uml.UMLEnvironmentFactory;
import org.eclipse.ocl.uml.UMLEvaluationEnvironment;
import org.eclipse.ocl.uml.options.EvaluationMode;
import org.eclipse.ocl.uml.options.UMLEvaluationOptions;
import org.eclipse.ocl.util.OCLUtil;
import org.eclipse.uml2.uml.CallOperationAction;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Comment;
import org.eclipse.uml2.uml.Constraint;
import org.eclipse.uml2.uml.EnumerationLiteral;
import org.eclipse.uml2.uml.InstanceSpecification;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.SendSignalAction;
import org.eclipse.uml2.uml.State;
import org.eclipse.uml2.uml.UMLFactory;
import org.eclipse.uml2.uml.UMLPackage;
/**
* Tests the {@link UMLEnvironment} class.
*
* @author Christian W. Damus (cdamus)
*/
@SuppressWarnings("nls")
public class UMLEnvironmentTest
extends AbstractTestSuite {
/**
* Tests extensibility of the OCL Ecore environment.
*/
public void test_extensibility_156360() {
OCL ocl = OCL.newInstance(new MyEnvironmentFactory());
OCL.Helper helper = ocl.createOCLHelper();
helper.setContext(getUMLString());
Constraint constraint = null;
try { // Double \ for Java and Double \ again for OCL String Literal
constraint = helper.createInvariant(
"self.regexMatch('\\\\d{3}-\\\\d{3}-\\\\d{3}') <> null");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
assertTrue(ocl.check("123-456-789", constraint));
assertFalse(ocl.check("123-4567-890", constraint));
assertFalse(ocl.check("123-abc-456", constraint));
}
/**
* Tests the instance-model evaluation mode.
*/
public void test_evaluationMode_instanceModel_194390() {
expectModified = true;
UMLEnvironmentFactory factory = new UMLEnvironmentFactory(resourceSet);
OCL ocl = OCL.newInstance(factory);
EvaluationOptions.setOption(ocl.getEvaluationEnvironment(),
UMLEvaluationOptions.EVALUATION_MODE,
EvaluationMode.INSTANCE_MODEL);
OCL.Helper helper = ocl.createOCLHelper();
helper.setContext(getMetaclass("Element"));
Constraint constraint = null;
try {
constraint = helper.createInvariant(
"self.oclIsKindOf(InstanceSpecification)");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
InstanceSpecification instance1 = (InstanceSpecification) fruitPackage.createPackagedElement(
null, UMLPackage.Literals.INSTANCE_SPECIFICATION);
instance1.getClassifiers().add(apple);
try {
// if we had treated this as an EObject (not an InstanceSpecification)
// then this would have checked true
assertFalse(ocl.check(instance1, constraint));
instance1.getClassifiers().add(getMetaclass("InstanceSpecification"));
// now we really are an instance of InstanceSpecification
assertTrue(ocl.check(instance1, constraint));
} finally {
// clean up
instance1.destroy();
}
}
/**
* Tests the Java-EObjects evaluation mode.
*/
public void test_evaluationMode_runtimeObjects_194390() {
expectModified = true;
UMLEnvironmentFactory factory = new UMLEnvironmentFactory(resourceSet);
OCL ocl = OCL.newInstance(factory);
EvaluationOptions.setOption(ocl.getEvaluationEnvironment(),
UMLEvaluationOptions.EVALUATION_MODE,
EvaluationMode.RUNTIME_OBJECTS);
OCL.Helper helper = ocl.createOCLHelper();
helper.setContext(getMetaclass("Element"));
Constraint constraint1 = null;
Constraint constraint2 = null;
try {
constraint1 = helper.createInvariant(
"self.oclIsKindOf(InstanceSpecification)");
constraint2 = helper.createInvariant(
"self.oclIsKindOf(ocltest::Apple)");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
InstanceSpecification instance1 = (InstanceSpecification) fruitPackage.createPackagedElement(
null, UMLPackage.Literals.INSTANCE_SPECIFICATION);
instance1.getClassifiers().add(apple);
try {
// now we really are an instance of InstanceSpecification
assertTrue(ocl.check(instance1, constraint1));
// if we had treated this as an InstanceSpecification (not an EObject)
// then this would have checked true
assertFalse(ocl.check(instance1, constraint2));
} finally {
// clean up
instance1.destroy();
}
}
/**
* Tests the adaptive evaluation mode.
*/
public void test_evaluationMode_adaptive_194390() {
expectModified = true;
UMLEnvironmentFactory factory = new UMLEnvironmentFactory(resourceSet);
OCL ocl = OCL.newInstance(factory);
EvaluationOptions.setOption(ocl.getEvaluationEnvironment(),
UMLEvaluationOptions.EVALUATION_MODE,
EvaluationMode.ADAPTIVE);
OCL.Helper helper = ocl.createOCLHelper();
helper.setContext(getMetaclass("Element"));
Constraint constraint = null;
Constraint ownerConstraint = null;
try {
constraint = helper.createInvariant(
"self.oclIsKindOf(InstanceSpecification)");
ownerConstraint = helper.createInvariant(
"self.owner.oclIsKindOf(InstanceSpecification)");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
InstanceSpecification instance1 = (InstanceSpecification) fruitPackage.createPackagedElement(
null, UMLPackage.Literals.INSTANCE_SPECIFICATION);
instance1.getClassifiers().add(apple);
Comment comment = instance1.createOwnedComment();
InstanceSpecification instance2 = (InstanceSpecification) fruitPackage.createPackagedElement(
null, UMLPackage.Literals.INSTANCE_SPECIFICATION);
instance2.getClassifiers().add(getMetaclass("InstanceSpecification"));
try {
// adaptive mode handles instance specification (M1 level) when it
// happens to be classified by the "InstanceSpecification" metaclass
assertTrue(ocl.check(instance2, constraint));
// adaptive mode handles Java instance (M0 level) based on context
// being a non-instance-like element
assertTrue(ocl.check(comment, ownerConstraint));
// adaptive mode does not handle Java instance when it is a
// value- or instance-specification (M0 level)
assertFalse(ocl.check(instance1, constraint));
} finally {
// clean up
instance2.destroy();
comment.destroy();
instance1.destroy();
}
}
/**
* Tests that nested environments correctly copy their parent options when
* inheriting the entire options map ({@link Customizable#getOptions()}).
*/
public void test_optionInheritance() {
Environment<Package, Classifier, Operation, Property, EnumerationLiteral, Parameter, State, CallOperationAction, SendSignalAction, Constraint, Class, EObject>
parent = ocl.getEnvironment();
// set option in parent environment
ParsingOptions.setOption(parent, ProblemOption.CLOSURE_ITERATOR,
ProblemHandler.Severity.INFO);
Map<Option<?>, Object> parentOptions =
OCLUtil.getAdapter(parent, Customizable.class).getOptions();
// check that the map has the option
assertSame(ProblemHandler.Severity.INFO, parentOptions.get(ProblemOption.CLOSURE_ITERATOR));
Environment<Package, Classifier, Operation, Property, EnumerationLiteral, Parameter, State, CallOperationAction, SendSignalAction, Constraint, Class, EObject>
child = parent.getFactory().createEnvironment(parent);
Map<Option<?>, Object> childOptions =
OCLUtil.getAdapter(child, Customizable.class).getOptions();
// check that the child's map has the option
assertSame(ProblemHandler.Severity.INFO, parentOptions.get(ProblemOption.CLOSURE_ITERATOR));
// all of the options should be the same
assertEquals(parentOptions, childOptions);
// but different maps
assertNotSame(parentOptions, childOptions);
// change option in child
ParsingOptions.setOption(child, ProblemOption.CLOSURE_ITERATOR,
ProblemHandler.Severity.ERROR);
childOptions = OCLUtil.getAdapter(child, Customizable.class).getOptions();
assertFalse(parentOptions.equals(childOptions));
assertSame(ProblemHandler.Severity.ERROR, childOptions.get(ProblemOption.CLOSURE_ITERATOR));
}
/**
* Tests the evaluation of inverse link navigation in instance models.
*/
public void test_linkNavigationInReverse_259630() {
UMLEnvironmentFactory factory = new UMLEnvironmentFactory(resourceSet);
OCL ocl = OCL.newInstance(factory);
EvaluationOptions
.setOption(ocl.getEvaluationEnvironment(),
UMLEvaluationOptions.EVALUATION_MODE,
EvaluationMode.INSTANCE_MODEL);
URI uri = getTestModelURI("/model/instances.uml");
Resource res = resourceSet.getResource(uri, true);
Package instancesPkg = (Package) EcoreUtil.getObjectByType(res
.getContents(), UMLPackage.Literals.PACKAGE);
Class classA = (Class) instancesPkg.getOwnedType("A");
Class classB = (Class) instancesPkg.getOwnedType("B");
InstanceSpecification anA = (InstanceSpecification) instancesPkg
.getPackagedElement("anA");
InstanceSpecification anotherA = (InstanceSpecification) instancesPkg
.getPackagedElement("anotherA");
InstanceSpecification aB = (InstanceSpecification) instancesPkg
.getPackagedElement("aB");
// test forward navigation to be sure
OCL.Helper helper = ocl.createOCLHelper();
helper.setContext(classA);
OCLExpression<Classifier> query = null;
try {
query = helper.createQuery("self.b");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
Object result = ocl.evaluate(anA, query);
assertEquals(
"Wrong result in forward direction", Collections.singleton(aB), result);
// now for the interesting test: inverse navigation
helper.setContext(classB);
try {
query = helper.createQuery("self.a");
} catch (Exception e) {
fail("Failed to parse: " + e.getLocalizedMessage());
}
result = ocl.evaluate(aB, query);
assertTrue(result instanceof Collection<?>);
Collection<?> collection = (Collection<?>) result;
assertEquals("Wrong number of results", 2, collection.size());
assertTrue("anA not in the results", collection.contains(anA));
assertTrue("anotherA not in the results", collection.contains(anotherA));
}
//
// Framework methods
//
class MyEnvironment extends UMLEnvironment {
Operation regexMatch;
// this constructor is used to initialize the root environment
MyEnvironment(EPackage.Registry registry, ResourceSet rset) {
super(registry, rset);
defineCustomOperations();
}
// this constructor is used to initialize child environments
MyEnvironment(MyEnvironment parent) {
super(parent);
// get the parent's custom operations
regexMatch = parent.regexMatch;
}
@Override
protected void setFactory(
EnvironmentFactory<Package, Classifier, Operation, Property, EnumerationLiteral, Parameter, State, CallOperationAction, SendSignalAction, Constraint, Class, EObject> factory) {
super.setFactory(factory);
}
// use the AbstractEnvironment's mechanism for defining "additional operations"
// to add our custom operation to OCL's String primitive type
private void defineCustomOperations() {
// pattern-matching operation
regexMatch = UMLFactory.eINSTANCE.createOperation();
regexMatch.setName("regexMatch");
regexMatch.setType(getOCLStandardLibrary().getString());
regexMatch.createOwnedParameter("pattern", getOCLStandardLibrary().getString());
regexMatch.setIsQuery(true);
// annotate it so that we will recognize it in the evaluation environment
regexMatch.addKeyword("MyEnvironment");
// define it as an additional operation on OCL String
addHelperOperation(getOCLStandardLibrary().getString(), regexMatch);
}
}
class MyEvaluationEnvironment extends UMLEvaluationEnvironment {
MyEvaluationEnvironment(UMLEnvironmentFactory factory) {
super(factory);
}
MyEvaluationEnvironment(
EvaluationEnvironment<Classifier, Operation, Property, Class, EObject> parent) {
super(parent);
}
@Override
public Object callOperation(Operation operation, int opcode, Object source, Object[] args) {
if (!operation.getKeywords().contains("MyEnvironment")) {
// not our custom regex operation
return super.callOperation(operation, opcode, source, args);
}
if ("regexMatch".equals(operation.getName())) {
Pattern pattern = Pattern.compile((String) args[0]);
Matcher matcher = pattern.matcher((String) source);
return matcher.matches()? matcher.group() : null;
}
throw new UnsupportedOperationException(); // unknown operation
}
}
class MyEnvironmentFactory extends UMLEnvironmentFactory {
@Override
public MyEnvironment createEnvironment() {
MyEnvironment result = new MyEnvironment(getEPackageRegistry(), getResourceSet());
result.setFactory(this);
return result;
}
@Override
public Environment<Package, Classifier, Operation, Property, EnumerationLiteral, Parameter, State, CallOperationAction, SendSignalAction, Constraint, Class, EObject>
createEnvironment(Environment<Package, Classifier, Operation, Property, EnumerationLiteral, Parameter, State, CallOperationAction, SendSignalAction, Constraint, Class, EObject> parent) {
if (!(parent instanceof MyEnvironment)) {
throw new IllegalArgumentException(
"Parent environment must be my environment: " + parent);
}
MyEnvironment result = new MyEnvironment((MyEnvironment) parent);
result.setFactory(this);
return result;
}
@Override
public EvaluationEnvironment<Classifier, Operation, Property, Class, EObject>
createEvaluationEnvironment() {
return new MyEvaluationEnvironment(this);
}
@Override
public EvaluationEnvironment<Classifier, Operation, Property, Class, EObject>
createEvaluationEnvironment(
EvaluationEnvironment<Classifier, Operation, Property, Class, EObject> parent) {
return new MyEvaluationEnvironment(parent);
}
}
}