/*******************************************************************************
* Copyright (c) 2005, 2012 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
*******************************************************************************/
package org.eclipse.ocl.ecore.tests;
import java.util.Collections;
import java.util.List;
import junit.framework.AssertionFailedError;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.ecore.CallOperationAction;
import org.eclipse.ocl.ecore.CollectionType;
import org.eclipse.ocl.ecore.Constraint;
import org.eclipse.ocl.ecore.EcoreEnvironment;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.SendSignalAction;
import org.eclipse.ocl.ecore.internal.UMLReflectionImpl;
import org.eclipse.ocl.expressions.AssociationClassCallExp;
import org.eclipse.ocl.expressions.FeatureCallExp;
import org.eclipse.ocl.expressions.LoopExp;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.OperationCallExp;
import org.eclipse.ocl.helper.Choice;
import org.eclipse.ocl.helper.ChoiceKind;
import org.eclipse.ocl.helper.ConstraintKind;
import org.eclipse.ocl.utilities.UMLReflection;
/**
* Tests for association navigation.
*
* @author Christian W. Damus (cdamus)
*/
@SuppressWarnings("nls")
public class AssociationTest
extends AbstractTestSuite {
EReference stem_apple;
EReference stem_tree;
EAttribute stem_thickness;
EReference apple_tree;
// EClass tree;
EReference tree_apples;
EClass forest;
EReference forest_trees;
EStructuralFeature q1;
EStructuralFeature q2;
/**
* Tests support for association end qualifiers.
*/
public void test_qualifiers_associationEnd_RATLC00538077() {
// allowed to specify no qualifiers
parse(
"package ocltest context Forest " +
"inv: self.trees->forAll(t : Tree | not t.apples->isEmpty())" +
" endpackage");
// allowed to specify both qualifiers
parse(
"package ocltest context Forest " +
"inv: not self.trees['foo', 3].apples->isEmpty()" +
" endpackage");
AssertionFailedError err = null;
try {
// must specify both qualifiers or none
parse(
"package ocltest context Forest " +
"inv: not self.trees['foo'].apples->isEmpty()" +
" endpackage");
} catch (AssertionFailedError e) {
// this is expected (success case)
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Parse should have failed", err);
try {
// qualifiers must conform
parse(
"package ocltest context Forest " +
"inv: not self.trees[3, 'foo'].apples->isEmpty()" +
" endpackage");
} catch (AssertionFailedError e) {
// this is expected (success case)
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Parse should have failed", err);
try {
// too many qualifiers
parse(
"package ocltest context Forest " +
"inv: not self.trees['foo', 3, 'bar'].apples->isEmpty()" +
" endpackage");
} catch (AssertionFailedError e) {
// this is expected (success case)
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Parse should have failed", err);
}
/**
* Tests support for association class navigation.
*/
public void test_associationClass_RATLC00538077() {
// navigate the association end as usual
parse(
"package ocltest context Tree " +
"inv: self.apples->forAll(a : Apple | a.color <> Color::black)" +
" endpackage");
// navigate to the association class, itself
parse(
"package ocltest context Tree " +
"inv: self.stem->forAll(s : Stem | s.thickness > 0)" +
" endpackage");
}
/**
* Tests support for association class navigation qualifiers.
*/
public void test_associationClass_qualifiers_RATLC00538077() {
// navigate to the association class using the optional qualifier
parse(
"package ocltest context Tree " +
"inv: self.stem[apples]->forAll(s : Stem | s.thickness > 0)" +
" endpackage");
AssertionFailedError err = null;
try {
// cannot use role name "tree" as a qualifier
parse(
"package ocltest context Tree " +
"inv: self.stem[tree]->forAll(s : Stem | s.thickness > 0)" +
" endpackage");
} catch (AssertionFailedError e) {
// this is expected (success case)
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Parse should have failed", err);
try {
// cannot use other expressions as qualifiers
parse(
"package ocltest context Tree " +
"inv: self.stem[3]->forAll(s : Stem | s.thickness > 0)" +
" endpackage");
} catch (AssertionFailedError e) {
// this is expected (success case)
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Parse should have failed", err);
try {
// only one qualifier is allowed
parse(
"package ocltest context Tree " +
"inv: self.stem[apples, apples]->forAll(s : Stem | s.thickness > 0)" +
" endpackage");
} catch (AssertionFailedError e) {
// this is expected (success case)
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Parse should have failed", err);
}
/**
* Tests support for association class navigation in the case of a
* reflexive association.
*/
public void test_associationClass_reflexive_RATLC00538077() {
// set up a reflexive association class for this test case
EClass ac = EcoreFactory.eINSTANCE.createEClass();
ac.setName("AC");
fruitPackage.getEClassifiers().add(ac);
EAttribute x = EcoreFactory.eINSTANCE.createEAttribute();
x.setName("x");
x.setEType(EcorePackage.eINSTANCE.getEInt());
ac.getEStructuralFeatures().add(x);
EReference parent = EcoreFactory.eINSTANCE.createEReference();
parent.setName("parent");
parent.setEType(tree);
tree.getEStructuralFeatures().add(parent);
EReference children = EcoreFactory.eINSTANCE.createEReference();
children.setName("children");
children.setEType(tree);
children.setUpperBound(-1);
tree.getEStructuralFeatures().add(children);
EReference parentTree = EcoreFactory.eINSTANCE.createEReference();
parentTree.setName("parentTree");
parentTree.setEType(tree);
parentTree.setEOpposite(parent);
ac.getEStructuralFeatures().add(parentTree);
EReference childTree = EcoreFactory.eINSTANCE.createEReference();
childTree.setName("childTree");
childTree.setEType(tree);
childTree.setEOpposite(children);
ac.getEStructuralFeatures().add(childTree);
// navigate the association end as usual
parse(
"package ocltest context Tree " +
"inv: self.children->forAll(t : Tree | not t.apples->isEmpty())" +
" endpackage");
// navigate to the association class, itself, using the role qualifier
parse(
"package ocltest context Tree " +
"inv: self.aC[children]->forAll(ac : AC | ac.x > 0)" +
" endpackage");
// this direction is a scalar reference
parse(
"package ocltest context Tree " +
"inv: self.aC[parent].x > 0" +
" endpackage");
AssertionFailedError err = null;
try {
// ambiguous navigation to association class
parse(
"package ocltest context Tree " +
"inv: self.aC->forAll(ac : AC | ac.x > 0)" +
" endpackage");
} catch (AssertionFailedError e) {
// this is expected (success case)
err = e;
System.out.println("Got expected error: " + e.getLocalizedMessage());
}
assertNotNull("Parse should have failed", err);
}
/**
* Tests support for navigation from association class to its ends.
*/
public void test_navigateFromAssociationClass_RATLC00538077() {
// navigate the association end. This end is multiplicity 1..1 by
// definition
parse(
"package ocltest context Stem " +
"inv: self.apple.color <> Color::black" +
" endpackage");
// this end, too, is 1..1
parse(
"package ocltest context Stem " +
"inv: self.tree.apples->forAll(a : Apple | a.color <> Color::black)" +
" endpackage");
// I have my own attributes, too
parse(
"package ocltest context Stem " +
"inv: self.thickness > 0" +
" endpackage");
}
/**
* Tests that content-assist suggests association class navigation.
*/
public void test_associationClass_contextAssist_RATLC00538077() {
helper.setContext(tree);
try {
List<Choice> choices = helper.getSyntaxHelp(
ConstraintKind.INVARIANT, "self.");
assertNotNull(choices);
// regular reference feature
assertChoice(choices, ChoiceKind.PROPERTY, "apples");
// association class navigation
assertChoice(choices, ChoiceKind.ASSOCIATION_CLASS, "stem");
} catch (Exception e) {
fail("Parse failed: " + e.getLocalizedMessage());
}
}
/**
* Tests the retention of location information for property names referenced
* by association end call expressions for association classes.
*/
public void test_propertyPositions_associationClassEndCall() {
final String exprString =
"self.apples->notEmpty()";
OCLExpression<EClassifier> constraint = createQuery(tree, exprString);
OperationCallExp<EClassifier, EOperation> notEmptyExp = LocationInformationTest.asOperationCall(
constraint);
FeatureCallExp<EClassifier> mpcExp = LocationInformationTest.asFeatureCall(
notEmptyExp.getSource());
LocationInformationTest.assertPropertyLocation(mpcExp,
exprString.indexOf("apples"),
exprString.indexOf("->"));
}
/**
* Tests the retention of location information for property names referenced
* by association class call expressions.
*/
public void test_propertyPositions_associationClassCall() {
final String exprString =
"self.stem[apples]->notEmpty()";
OCLExpression<EClassifier> constraint = createQuery(tree, exprString);
OperationCallExp<EClassifier, EOperation> notEmptyExp = LocationInformationTest.asOperationCall(
constraint);
FeatureCallExp<EClassifier> mpcExp = LocationInformationTest.asFeatureCall(
notEmptyExp.getSource());
LocationInformationTest.assertPropertyLocation(mpcExp,
exprString.indexOf("stem"),
exprString.indexOf("["));
}
/**
* Tests the retention of location information for property names referenced
* by association class call expressions, where the source is a collection
* (so that there is an implicit collect node).
*/
public void test_propertyPositions_associationClassCall_implicitCollect() {
final String exprString =
"Tree.allInstances().stem[apples]->notEmpty()";
OCLExpression<EClassifier> constraint = createQuery(tree, exprString);
OperationCallExp<EClassifier, EOperation> notEmptyExp = LocationInformationTest.asOperationCall(
constraint);
// the OCL is implicitly ->collect(stem[apples])->notEmpty()
LoopExp<EClassifier, EParameter> loopExp = LocationInformationTest.asLoop(
notEmptyExp.getSource());
FeatureCallExp<EClassifier> mpcExp = LocationInformationTest.asFeatureCall(
loopExp.getBody());
LocationInformationTest.assertPropertyLocation(mpcExp,
exprString.indexOf("stem"),
exprString.indexOf("["));
}
/**
* Tests the retention of location information for property names referenced
* as qualifiers of association class call expressions.
*/
public void test_propertyPositions_associationClassQualified() {
final String exprString =
"self.stem[apples]->notEmpty()";
OCLExpression<EClassifier> constraint = createQuery(tree, exprString);
OperationCallExp<EClassifier, EOperation> notEmptyExp = LocationInformationTest.asOperationCall(
constraint);
AssociationClassCallExp<EClassifier, EStructuralFeature> accExp = LocationInformationTest.asAssociationClassCall(
notEmptyExp.getSource());
List<OCLExpression<EClassifier>> qualifiers = accExp.getQualifier();
assertEquals(1, qualifiers.size());
OCLExpression<EClassifier> qualifier = qualifiers.get(0);
LocationInformationTest.assertLocation(qualifier,
exprString.indexOf("apples"),
exprString.indexOf("]->"));
}
/**
* Tests the retention of location information for property names referenced
* as qualifiers of association class call expressions, where the source
* is a collection (so that there is an implicit collect node).
*/
public void test_propertyPositions_associationClassQualified_implicitCollect() {
final String exprString =
"Tree.allInstances().stem[apples]->notEmpty()";
OCLExpression<EClassifier> constraint = createQuery(tree, exprString);
OperationCallExp<EClassifier, EOperation> notEmptyExp = LocationInformationTest.asOperationCall(
constraint);
// the OCL is implicitly ->collect(stem[apples])->notEmpty()
LoopExp<EClassifier, EParameter> loopExp = LocationInformationTest.asLoop(
notEmptyExp.getSource());
AssociationClassCallExp<EClassifier, EStructuralFeature> accExp = LocationInformationTest.asAssociationClassCall(
loopExp.getBody());
List<OCLExpression<EClassifier>> qualifiers = accExp.getQualifier();
assertEquals(1, qualifiers.size());
OCLExpression<EClassifier> qualifier = qualifiers.get(0);
LocationInformationTest.assertLocation(qualifier,
exprString.indexOf("apples"),
exprString.indexOf("]->"));
}
/**
* Tests that navigation through association ends does not throws NPE
* in case that at least one end in the navigation path
* (except for the last) is undefined.
*/
public void test_associationNullContext_bugzilla121614() {
EPackage epackage = EcoreFactory.eINSTANCE.createEPackage();
epackage.setName("MyPackage");
epackage.setNsPrefix("mypkg");
epackage.setNsURI("http:///mypkg.ecore");
resourceSet.getPackageRegistry().put(epackage.getNsURI(), epackage);
// Employee class
EClass employee = EcoreFactory.eINSTANCE.createEClass();
employee.setName("Employee");
epackage.getEClassifiers().add(employee);
// manager reference
EReference manager = EcoreFactory.eINSTANCE.createEReference();
manager.setName("manager");
manager.setEType(employee);
employee.getEStructuralFeatures().add(manager);
EFactory efactory = epackage.getEFactoryInstance();
// create our test instance
EObject emp1 = efactory.create(employee);
// parse & evaluate expression
OCLExpression<EClassifier> expr = parse(
"package mypkg context Employee " +
"inv: self.manager.manager" +
" endpackage");
Object result = evaluate(expr, emp1);
assertInvalid(result);
}
/**
* Tests that the result of qualifying an association navigation is a
* scalar value (not a collection).
*/
public void test_qualifiedAssociation_scalar_133435() {
// unqualified navigation
OCLExpression<EClassifier> expr = parse(
"package ocltest context Forest " +
"inv: self.trees" +
" endpackage");
assertTrue(expr.getType() instanceof CollectionType);
CollectionType collType = (CollectionType) expr.getType();
assertSame(tree, collType.getElementType());
// qualified navigation
expr = parse(
"package ocltest context Forest " +
"inv: self.trees['foo', 3]" +
" endpackage");
assertSame(tree, expr.getType());
}
//
// Fixture methods
//
/**
* Sets up a common fixture for the association class tests.
*/
@Override
public void setUp() {
super.setUp();
expectModified = true;
initFruitExtensions();
}
private void initFruitExtensions() {
if (q1 != null) {
// already inited
return;
}
fruitPackage.getEClassifiers().remove(stem);
// "stem" is more appropriately modeled as an association class
stem = EcoreFactory.eINSTANCE.createEClass();
stem.setName("Stem");
// tree already created by initFruitPackage
// tree = EcoreFactory.eINSTANCE.createEClass();
// tree.setName("Tree");
fruitPackage.getEClassifiers().add(stem);
// fruitPackage.getEClassifiers().add(tree);
apple.getEStructuralFeatures().remove(apple_stem);
apple_stem = null;
apple_tree = EcoreFactory.eINSTANCE.createEReference();
apple_tree.setName("tree");
apple_tree.setEType(tree);
apple.getEStructuralFeatures().add(apple_tree);
tree_apples = EcoreFactory.eINSTANCE.createEReference();
tree_apples.setName("apples");
tree_apples.setEType(apple);
tree_apples.setUpperBound(-1);
tree.getEStructuralFeatures().add(tree_apples);
stem_thickness = EcoreFactory.eINSTANCE.createEAttribute();
stem_thickness.setName("thickness");
stem_thickness.setEType(EcorePackage.eINSTANCE.getEInt());
stem.getEStructuralFeatures().add(stem_thickness);
stem_apple = EcoreFactory.eINSTANCE.createEReference();
stem_apple.setName("apple");
stem_apple.setEType(apple);
stem.getEStructuralFeatures().add(stem_apple);
stem_tree = EcoreFactory.eINSTANCE.createEReference();
stem_tree.setName("tree");
stem_tree.setEType(tree);
stem.getEStructuralFeatures().add(stem_tree);
// add a Forest::trees association with qualifiers
forest = EcoreFactory.eINSTANCE.createEClass();
forest.setName("Forest");
fruitPackage.getEClassifiers().add(forest);
forest_trees = EcoreFactory.eINSTANCE.createEReference();
forest_trees.setName("trees");
forest_trees.setEType(tree);
forest_trees.setUpperBound(-1);
forest.getEStructuralFeatures().add(forest_trees);
q1 = EcoreFactory.eINSTANCE.createEAttribute();
q1.setName("q1");
q1.setEType(EcorePackage.eINSTANCE.getEString());
q2 = EcoreFactory.eINSTANCE.createEAttribute();
q2.setName("q2");
q2.setEType(EcorePackage.eINSTANCE.getEInt());
}
@Override
protected OCL createOCL() {
return OCL.newInstance(new AssocClassFruitEnvironmentFactory());
}
@Override
protected void tearDown()
throws Exception {
super.tearDown();
// delete customizations, to re-initialize schema for subsequent tests
fruitPackage = null;
}
private class AssocClassFruitEnvironmentFactory extends EcoreEnvironmentFactory {
public AssocClassFruitEnvironmentFactory() {
super(resourceSet.getPackageRegistry());
}
@Override
public EcoreEnvironment createEnvironment() {
return new AssocClassFruitEnvironment(this);
}
@Override
public EcoreEnvironment createEnvironment(
Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent) {
return new AssocClassFruitEnvironment(parent);
}
}
private class AssocClassFruitEnvironment extends EcoreEnvironment implements InitEnvironment {
private List<EStructuralFeature> stemEnds =
new java.util.ArrayList<EStructuralFeature>(2);
private List<EStructuralFeature> qualifiers =
new java.util.ArrayList<EStructuralFeature>(2);
public AssocClassFruitEnvironment(AssocClassFruitEnvironmentFactory factory) {
super(factory, null);
setContextPackage(fruitPackage);
// init();
}
public AssocClassFruitEnvironment(
Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent) {
super(parent);
// init();
}
public void init() {
initFruitExtensions();
stemEnds.add(stem_tree);
stemEnds.add(stem_apple);
qualifiers.add(q1);
qualifiers.add(q2);
}
@Override
public UMLReflection<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint> getUMLReflection() {
return new UMLReflectionImpl() {
@Override
public boolean isAssociationClass(EClassifier eclass) {
return eclass == stem || eclass.getName().equals("AC");
}
@Override
public EClass getAssociationClass(EStructuralFeature reference) {
if (reference == tree_apples || reference == apple_tree) {
return stem;
} else if (reference.getName().equals("parent")
|| reference.getName().equals("children")) {
return (EClass) fruitPackage.getEClassifier("AC");
}
return null;
}
@Override
public List<EStructuralFeature> getMemberEnds(
EClassifier associationClass) {
if (associationClass == stem) {
return stemEnds;
} else if (associationClass.getName().equals("AC")) {
EClass ac = (EClass) associationClass;
List<EStructuralFeature> result = new java.util.ArrayList<EStructuralFeature>(2);
result.add(ac.getEStructuralFeature("parentTree"));
result.add(ac.getEStructuralFeature("childTree"));
return result;
}
return Collections.emptyList();
}
@Override
public List<EStructuralFeature> getQualifiers(EStructuralFeature property) {
if (property == forest_trees) {
return qualifiers;
}
return Collections.emptyList();
}};
}
}
}