| /******************************************************************************* |
| * Copyright (c) 2006, 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 |
| * Zeligsoft - Bugs 247079, 251808 |
| *******************************************************************************/ |
| |
| package org.eclipse.ocl.uml.util; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.emf.common.util.BasicEList; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.common.util.UniqueEList; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EcorePackage; |
| 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.uml.internal.UMLForeignMethods; |
| import org.eclipse.uml2.common.util.UML2Util; |
| import org.eclipse.uml2.uml.Class; |
| import org.eclipse.uml2.uml.Classifier; |
| import org.eclipse.uml2.uml.Element; |
| import org.eclipse.uml2.uml.NamedElement; |
| import org.eclipse.uml2.uml.Namespace; |
| import org.eclipse.uml2.uml.Operation; |
| import org.eclipse.uml2.uml.Package; |
| import org.eclipse.uml2.uml.Profile; |
| import org.eclipse.uml2.uml.ProfileApplication; |
| import org.eclipse.uml2.uml.Property; |
| import org.eclipse.uml2.uml.Reception; |
| import org.eclipse.uml2.uml.Stereotype; |
| import org.eclipse.uml2.uml.UMLPackage; |
| import org.eclipse.uml2.uml.resource.UMLResource; |
| import org.eclipse.uml2.uml.util.UMLUtil; |
| |
| /** |
| * Utilities for resolving references to packages against a resource set and |
| * finding the Ecore definitions of UML packages and classifiers in support of |
| * evaluation of OCL expressions. |
| * |
| * @author Christian W. Damus (cdamus) |
| */ |
| public class OCLUMLUtil extends UMLUtil { |
| /** |
| * The shared UML binding plugin identification. |
| * @since 4.0 |
| */ |
| public static final String PLUGIN_ID = "org.eclipse.ocl.uml"; //$NON-NLS-1$ |
| |
| // not instantiable by clients |
| private OCLUMLUtil() { |
| super(); |
| } |
| |
| /** |
| * <i>Foreign method</i> for the <tt>getAllReceptions()</tt> query that |
| * is missing from the UML metamodel. |
| * |
| * @param clazz a UML class |
| * |
| * @return the receptions that it defines and/or inherits |
| */ |
| public static List<Reception> getAllReceptions(Class clazz) { |
| List<Reception> result = new BasicEList.FastCompare<Reception>(); |
| |
| for (NamedElement m : getInheritedMembers(clazz)) { |
| if (m instanceof Reception) { |
| result.add((Reception) m); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Obtains the UML metaclass of the specified model element. The specified |
| * element must be in a resource set, otherwise the UML metamodel is not |
| * available. |
| * |
| * @param element an element in a UML model |
| * @return the element's metaclass, or <code>null</code> if the element is |
| * not in a resource set |
| */ |
| public static Class getMetaclass(Element element) { |
| Class result = null; |
| |
| ResourceSet rset = null; |
| Resource res = element.eResource(); |
| if (res != null) { |
| rset = res.getResourceSet(); |
| } |
| |
| if (rset != null) { |
| result = (Class) getClassifier(element.eClass(), rset); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Obtains the UML classifier corresponding to the specified Ecore classifier, |
| * which is the type of some object in an OCL expression evaluation. |
| * |
| * @param eclassifier an Ecore definition of a UML classifier |
| * @param rset the resource set in which to look for the corresponding UML |
| * classifier |
| * |
| * @return the corresponding UML classifier, or <code>null</code> if not found |
| */ |
| public static Classifier getClassifier(EClassifier eclassifier, ResourceSet rset) { |
| Classifier result = null; |
| EPackage epackage = eclassifier.getEPackage(); |
| |
| // easy case is the profile definition |
| Profile profile = getProfile(epackage); |
| if (profile != null) { |
| NamedElement ne = getNamedElement(eclassifier); |
| if (ne instanceof Classifier) { |
| result = (Classifier) ne; |
| } |
| } else { |
| Package umlPackage = null; |
| |
| if (epackage == UMLPackage.eINSTANCE) { |
| // easy. We know where the UML metamodel, itself, is |
| umlPackage = getUMLMetamodel(rset); |
| } else if (epackage == EcorePackage.eINSTANCE) { |
| // another easy one |
| umlPackage = getEcoreMetamodel(rset); |
| } |
| |
| if (umlPackage == null) { |
| // look for the UML model describing a generated EPackage |
| List<String> names = getQualifiedName(epackage); |
| |
| umlPackage = findPackage(names, rset); |
| } |
| |
| if (umlPackage != null) { |
| result = (Classifier) umlPackage.getMember( |
| eclassifier.getName(), |
| false, |
| UMLPackage.Literals.CLASSIFIER); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Computes the qualified name of an Ecore package. |
| * |
| * @param epackage the Ecore package |
| * @return its qualified name |
| */ |
| private static List<String> getQualifiedName(EPackage epackage) { |
| List<String> result = new java.util.LinkedList<String>(); |
| |
| while (epackage != null) { |
| result.add(0, epackage.getName()); |
| epackage = epackage.getESuperPackage(); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Searches the specified resource set for a UML package. |
| * |
| * @param packageNames the qualified package name |
| * @param rset the resource set to search |
| * @return the matching package, or <code>null</code> if not found |
| */ |
| public static Package findPackage(List<String> packageNames, ResourceSet rset) { |
| if (packageNames.isEmpty()) { |
| return null; |
| } |
| |
| String firstName = packageNames.get(0); |
| for (Resource res : rset.getResources()) { |
| for (EObject root : res.getContents()) { |
| if (root instanceof Package) { |
| Package pkg = (Package) root; |
| |
| // we are only looking for root packages |
| if ((pkg.getOwner() == null) |
| && UMLForeignMethods.isNamed(firstName, pkg)) { |
| for (String name : packageNames.subList(1, packageNames.size())) { |
| pkg = UMLForeignMethods.getNestedPackage(pkg, name); |
| |
| if (pkg == null) { |
| return null; |
| } |
| } |
| |
| return pkg; |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Searches the specified resource set for a UML namespace (not necessarily |
| * a package). |
| * |
| * @param qualifiedName the qualified namespace name |
| * @param rset the resource set to search |
| * @return the matching namespace, or <code>null</code> if not found |
| */ |
| public static Namespace findNamespace(List<String> qualifiedName, ResourceSet rset) { |
| if (qualifiedName.isEmpty()) { |
| return null; |
| } |
| |
| Namespace result = null; |
| |
| String firstName = qualifiedName.get(0); |
| for (Resource res : rset.getResources()) { |
| for (EObject root : res.getContents()) { |
| if (root instanceof Package) { |
| Namespace ns = (Package) root; |
| |
| // we are only looking for root namespaces |
| if ((ns.getOwner() == null) |
| && UMLForeignMethods.isNamed(firstName, ns)) { |
| for (String name : qualifiedName.subList(1, qualifiedName.size())) { |
| ns = (Namespace) UMLForeignMethods.getMember( |
| ns, |
| name, |
| UMLPackage.Literals.NAMESPACE); |
| |
| if (ns == null) { |
| break; |
| } |
| } |
| |
| if (ns != null) { |
| result = ns; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (result != null) { |
| break; |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Searches the specified package registry set for an Ecore definition of |
| * the specified UML package. If this package registry doesn't have it and |
| * is not the global package registry, then the global registry is also |
| * searched. |
| * |
| * @param pkg the UML package |
| * @param registry the registry in which to look for the Ecore definition |
| * @return the matching Ecore definition, or <code>null</code> if not found |
| */ |
| public static EPackage getEPackage(Package pkg, EPackage.Registry registry) { |
| EPackage result = null; |
| |
| Stereotype epkgStereo = pkg.getAppliedStereotype("Ecore::EPackage"); //$NON-NLS-1$ |
| if (epkgStereo != null) { |
| String nsURI = (String) pkg.getValue( |
| epkgStereo, |
| UMLUtil.TAG_DEFINITION__NS_URI); |
| |
| if (nsURI != null) { |
| result = registry.getEPackage(nsURI); |
| } |
| } |
| |
| // no point in looking up a package that hasn't a valid qualified name |
| if ((result == null) && (pkg.getQualifiedName() != null)) { |
| List<String> names = new java.util.LinkedList<String>(); |
| while (pkg != null) { |
| names.add(0, pkg.getName()); |
| pkg = pkg.getNestingPackage(); |
| } |
| |
| result = findPackage(names, registry); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Looks up the Ecore definition of the specified UML classifier, using the |
| * specified <code>element</code> as a context for finding profile |
| * applications in the case that the classifier is a stereotype or some |
| * other type in a {@link Profile}. Finding the Ecore definition of a profile |
| * type requires finding the actual applied version of the profile. |
| * |
| * @param umlClassifier a UML classifier |
| * @param element an element in the context of which the OCL evaluation |
| * is being performed |
| * @param registry the package registry in which to search for the Ecore |
| * definition |
| * @return the corresponding Ecore classifier, or <code>null</code> if not |
| * found |
| */ |
| public static EClassifier getEClassifier( |
| Classifier umlClassifier, |
| Object element, |
| EPackage.Registry registry) { |
| |
| EClassifier result = null; |
| Package umlPackage = umlClassifier.getPackage(); |
| EPackage ecorePackage = null; |
| |
| if (umlPackage instanceof Profile) { |
| // use the element to get the most appropriate profile definition |
| Profile profile = (Profile) umlPackage; |
| |
| if (element instanceof Element) { |
| Element umlElement = (Element) element; |
| |
| Package nesting = umlElement.getNearestPackage(); |
| while (nesting != null) { |
| ProfileApplication appl = nesting.getProfileApplication(profile); |
| if (appl != null) { |
| ecorePackage = appl.getAppliedDefinition(); |
| break; |
| } |
| |
| nesting = (nesting.getOwner() == null) |
| ? null |
| : nesting.getOwner().getNearestPackage(); |
| } |
| } |
| |
| if (ecorePackage == null) { |
| // assume the latest definition of the profile (if any) |
| ecorePackage = profile.getDefinition(); |
| } |
| } else if (umlPackage != null) { |
| ecorePackage = getEPackage(umlPackage, registry); |
| } |
| |
| if (ecorePackage != null) { |
| result = ecorePackage.getEClassifier( |
| UML2Util.getValidJavaIdentifier(umlClassifier.getName())); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Looks in the given registry for a package with the specified qualified |
| * package name. |
| * |
| * @param packageNames the qualified package name |
| * @return the matching EPackage, or <code>null</code> if not found |
| */ |
| protected static EPackage findPackage( |
| List<String> packageNames, |
| EPackage.Registry registry) { |
| |
| if (packageNames.isEmpty()) { |
| return null; |
| } |
| |
| String name = packageNames.get(0); |
| for (Object next : registry.values()) { |
| if (next instanceof EPackage) { |
| EPackage ePackage = (EPackage) next; |
| |
| // only consider root-level packages when searching by name |
| if ((ePackage.getESuperPackage() == null) |
| && name.equals(ePackage.getName())) { |
| |
| EPackage tentativeResult = findNestedPackage( |
| packageNames.subList(1, packageNames.size()), |
| ePackage); |
| |
| if (tentativeResult != null) { |
| return tentativeResult; |
| } |
| } |
| } |
| } |
| |
| EPackage result = findPackageByNSPrefix(packageNames, registry); |
| |
| if ((result == null) && (registry != EPackage.Registry.INSTANCE)) { |
| result = findPackage(packageNames, EPackage.Registry.INSTANCE); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Looks in the given Ecore package for a nested package with the specified |
| * relative package name. |
| * |
| * @param packageNames the relative package name |
| * @param package the starting package to look in |
| * @return the matching Ecore package, or <code>null</code> if not found |
| */ |
| private static EPackage findNestedPackage( |
| List<String> packageNames, |
| EPackage superpackage) { |
| |
| if (packageNames.isEmpty()) { |
| // stopping condition |
| return superpackage; |
| } |
| |
| String name = packageNames.get(0); |
| |
| for (EPackage next : superpackage.getESubpackages()) { |
| if (name.equals(next.getName())) { |
| EPackage tentativeResult = findNestedPackage( |
| packageNames.subList(1, packageNames.size()), |
| next); |
| |
| if (tentativeResult != null) { |
| return tentativeResult; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Looks in the given registry for a package with the specified qualified |
| * package name, matching the name against the namespace prefixes of the |
| * packages in the registry. |
| * |
| * @param packageNames the qualified package name |
| * @return the NSPrefix-matching EPackage, or <code>null</code> if not found |
| */ |
| private static EPackage findPackageByNSPrefix( |
| List<String> packageNames, |
| EPackage.Registry registry) { |
| |
| StringBuilder stringBuffer = new StringBuilder(); |
| Iterator<String> it = packageNames.iterator(); |
| while (it.hasNext()) { |
| stringBuffer.append(it.next()); |
| if (it.hasNext()) { |
| stringBuffer.append(".");//$NON-NLS-1$ |
| } |
| } |
| |
| String nsPrefix = stringBuffer.toString(); |
| |
| for (Object next : registry.values()) { |
| if (next instanceof EPackage) { |
| EPackage ePackage = (EPackage) next; |
| if (nsPrefix.equals(ePackage.getNsPrefix())) { |
| return ePackage; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Obtains the UML metamodel library loaded in the specified resource set, |
| * loading it if necessary. |
| * |
| * @param resourceSet a resource set |
| * @return the UML metamodel |
| */ |
| public static Package getUMLMetamodel(ResourceSet resourceSet) { |
| Package result = null; |
| Resource res = resourceSet.getResource( |
| URI.createURI(UMLResource.UML_METAMODEL_URI), true); |
| |
| if (res != null) { |
| result = (Package) EcoreUtil.getObjectByType( |
| res.getContents(), |
| UMLPackage.Literals.PACKAGE); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Obtains the Ecore metamodel library loaded in the specified resource set, |
| * loading it if necessary. |
| * |
| * @param resourceSet a resource set |
| * @return the Ecore metamodel |
| */ |
| static Package getEcoreMetamodel(ResourceSet resourceSet) { |
| Package result = null; |
| Resource res = resourceSet.getResource( |
| URI.createURI(UMLResource.ECORE_METAMODEL_URI), true); |
| |
| if (res != null) { |
| result = (Package) EcoreUtil.getObjectByType( |
| res.getContents(), |
| UMLPackage.Literals.PACKAGE); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * A foreign method for the {@link Classifier} that obtains all members, |
| * inherited or not, regardless of visibility, for OCL purposes. |
| * |
| * @param classifier a classifier |
| * @return all of its members, including inheritable and non-inheritable |
| * members (private members of general types) except for those |
| * that are redefined |
| * |
| * @since 1.2 |
| */ |
| private static EList<NamedElement> getInheritedMembers(Classifier classifier) { |
| EList<NamedElement> result = new UniqueEList.FastCompare<NamedElement>( |
| classifier.getMembers()); |
| for (Classifier parent : classifier.allParents()) { |
| if (parent != classifier) { |
| result.addAll(parent.getMembers()); |
| } |
| } |
| |
| // filter out redefined members |
| result = classifier.inherit(result); |
| |
| return result; |
| } |
| |
| /** |
| * A foreign method for the {@link Classifier} that obtains all attributes |
| * regardless of visibility, for OCL purposes. |
| * |
| * @param classifier a classifier |
| * @return all of its attributes, including inheritable and non-inheritable |
| * attributes (private attributes of general types) except for those |
| * that are redefined |
| * |
| * @since 1.2 |
| */ |
| public static EList<Property> getAllAttributes(Classifier classifier) { |
| EList<NamedElement> members = getInheritedMembers(classifier); |
| |
| EList<Property> result = new BasicEList<Property>(members.size()); |
| for (NamedElement next : members) { |
| if (next instanceof Property) { |
| result.add((Property) next); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * A foreign method for the {@link Property} that queries whether it is a |
| * non-navigable association end of a given classifier. This means that the |
| * end is association-owned and opposite to a classifier-owned end whose |
| * type is a supertype of the specified classifier. |
| * |
| * @param associationEnd |
| * the purported non-navigable association end |
| * @param endClassifier |
| * a classifier from which we purport to navigate the association |
| * @return <code>true</code> if the association-end is a non-navigable end |
| * of the classifier; <code>false</code>, otherwise |
| * |
| * @since 2.0 |
| */ |
| public static boolean isNonNavigableAssocationEndOf( |
| Property associationEnd, Classifier endClassifier) { |
| |
| boolean result = associationEnd.getOwningAssociation() != null; |
| if (result) { |
| Property otherEnd = associationEnd.getOtherEnd(); |
| |
| result = (otherEnd != null) && (otherEnd.getType() != null) |
| && endClassifier.conformsTo(otherEnd.getType()); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * A foreign method for the {@link Classifier} that obtains all operations |
| * regardless of visibility, for OCL purposes. |
| * |
| * @param classifier a classifier |
| * @return all of its operations, including inheritable and non-inheritable |
| * operations (private operations of general types) except for those |
| * that are redefined |
| * |
| * @since 1.2 |
| */ |
| public static EList<Operation> getAllOperations(Classifier classifier) { |
| EList<NamedElement> members = getInheritedMembers(classifier); |
| |
| EList<Operation> result = new BasicEList<Operation>(members.size()); |
| for (NamedElement next : members) { |
| if (next instanceof Operation) { |
| result.add((Operation) next); |
| } |
| } |
| |
| return result; |
| } |
| } |