diff options
author | Sebastien Gabel | 2016-07-11 16:01:26 +0000 |
---|---|---|
committer | Gerrit Code Review @ Eclipse.org | 2016-07-13 12:29:24 +0000 |
commit | f68c766e5c5df1bb5c08fd65bc6f5464d3a58208 (patch) | |
tree | 11bcd52a964d8879f633f0b0d34935b62099f784 /plugins/uml/tools/org.eclipse.papyrus.uml.tools.utils/src | |
parent | a47bee0d51e731024be5f4385b5f32ac80ee6be9 (diff) | |
download | org.eclipse.papyrus-f68c766e5c5df1bb5c08fd65bc6f5464d3a58208.tar.gz org.eclipse.papyrus-f68c766e5c5df1bb5c08fd65bc6f5464d3a58208.tar.xz org.eclipse.papyrus-f68c766e5c5df1bb5c08fd65bc6f5464d3a58208.zip |
Bug 497699: NPE in ProfileUtil
Change-Id: I4f177f2e07e88058194a2c3ec50781b7e77c63a8
Signed-off-by: Sebastien Gabel <sebastien.gabel@esterel-technologies.com>
Diffstat (limited to 'plugins/uml/tools/org.eclipse.papyrus.uml.tools.utils/src')
-rw-r--r-- | plugins/uml/tools/org.eclipse.papyrus.uml.tools.utils/src/org/eclipse/papyrus/uml/tools/utils/ProfileUtil.java | 815 |
1 files changed, 408 insertions, 407 deletions
diff --git a/plugins/uml/tools/org.eclipse.papyrus.uml.tools.utils/src/org/eclipse/papyrus/uml/tools/utils/ProfileUtil.java b/plugins/uml/tools/org.eclipse.papyrus.uml.tools.utils/src/org/eclipse/papyrus/uml/tools/utils/ProfileUtil.java index c043f8da0d5..f1a9d1928e1 100644 --- a/plugins/uml/tools/org.eclipse.papyrus.uml.tools.utils/src/org/eclipse/papyrus/uml/tools/utils/ProfileUtil.java +++ b/plugins/uml/tools/org.eclipse.papyrus.uml.tools.utils/src/org/eclipse/papyrus/uml/tools/utils/ProfileUtil.java @@ -1,407 +1,408 @@ -/*****************************************************************************
- * Copyright (c) 2012, 2015 CEA LIST, Christian W. Damus, 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:
- * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
- * Christian W. Damus (CEA) - Handle dynamic profile applications in CDO
- * Christian W. Damus - bug 399859
- * Christian W. Damus - bug 451613
- * Christian W. Damus - bug 474610
- *
- *****************************************************************************/
-package org.eclipse.papyrus.uml.tools.utils;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.EClass;
-import org.eclipse.emf.ecore.EClassifier;
-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.resource.URIConverter;
-import org.eclipse.emf.ecore.util.EcoreUtil;
-import org.eclipse.papyrus.infra.emf.utils.EMFHelper;
-import org.eclipse.uml2.common.util.UML2Util;
-import org.eclipse.uml2.uml.Association;
-import org.eclipse.uml2.uml.AttributeOwner;
-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.Operation;
-import org.eclipse.uml2.uml.OperationOwner;
-import org.eclipse.uml2.uml.Package;
-import org.eclipse.uml2.uml.PackageableElement;
-import org.eclipse.uml2.uml.Parameter;
-import org.eclipse.uml2.uml.Profile;
-import org.eclipse.uml2.uml.ProfileApplication;
-import org.eclipse.uml2.uml.Property;
-import org.eclipse.uml2.uml.Stereotype;
-import org.eclipse.uml2.uml.UMLPackage;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.SetMultimap;
-
-/**
- * A class containing static utility method regarding UML profiles
- *
- * @author Camille Letavernier
- *
- */
-public class ProfileUtil extends org.eclipse.uml2.uml.util.UMLUtil {
-
-
- /**
- * Checks if the profile applied has been changed since last application (definition does not match.
- *
- * @param _package
- * on which the profile is applied
- * @param _profile
- * the applied profile
- * @return true if the profile has changed
- */
- public static boolean isDirty(Package _package, Profile _profile) {
- boolean isDirty = false;
- if (_profile == null || _profile.eResource() == null || _package.eResource() == null) {
- return false;
- }
-
- // Retrieve model resourceSet
- ResourceSet pkge_resourceSet = _package.eResource().getResourceSet();
-
- if (pkge_resourceSet != null) {
-
- // Retrieve profile resource
- URI prof_URI = _profile.eResource().getURI();
- Resource modelResource = pkge_resourceSet.getResource(prof_URI, true);
-
- if (modelResource != null && modelResource.getContents().get(0) instanceof Profile) {
-
- // ckeck applied profile application definition vs profile definition referenced in file
- Profile profileInFile = (Profile) (modelResource.getContents().get(0));
- ProfileApplication profileApplication = _package.getProfileApplication(_profile, true);
- if (profileApplication != null) {
- EPackage appliedProfileDefinition = profileApplication.getAppliedDefinition();
- EPackage fileProfileDefinition = null;
-
- // Check profiles qualified names to ensure the correct profiles are compared
- String appliedProfileName = _profile.getQualifiedName();
- String fileProfileName = profileInFile.getQualifiedName();
- if (!appliedProfileName.equals(fileProfileName)) {
-
- // The profile must be a subprofile
- Iterator<Profile> it = PackageUtil.getSubProfiles(profileInFile).iterator();
- while (it.hasNext()) {
- Profile current = it.next();
- fileProfileName = current.getQualifiedName();
- if (fileProfileName.equals(appliedProfileName)) {
- profileInFile = current;
- }
- }
- }
-
- fileProfileDefinition = profileInFile.getDefinition();
-
- // don't just test that the EPackage definitions are the
- // same object because in the CDO context they are not, even
- // though they are "the same package". Comparing the URIs
- // should suffice
- final URIConverter converter = pkge_resourceSet.getURIConverter();
- if ((appliedProfileDefinition == null)
- || (fileProfileDefinition == null)
- || !UML2Util.safeEquals(
- converter.normalize(EcoreUtil.getURI(appliedProfileDefinition)),
- converter.normalize(EcoreUtil.getURI(fileProfileDefinition)))) {
-
- isDirty = true;
- }
- }
-
- }
- }
-
-
-
- return isDirty;
- }
-
- public static List<Stereotype> findAllSubStereotypes(Stereotype parentStereotype, Package umlPackage, boolean concreteOnly) {
- Collection<Stereotype> result = new LinkedHashSet<Stereotype>();
-
- for (Profile profile : umlPackage.getAllAppliedProfiles()) {
- List<Stereotype> allStereotypes = new LinkedList<Stereotype>();
- findAllStereotypes(profile, allStereotypes);
- for (Stereotype stereotype : allStereotypes) {
- if (concreteOnly && stereotype.isAbstract()) {
- continue; // Skip abstract stereotypes
- }
-
- if (isSubStereotype(parentStereotype, stereotype)) {
- result.add(stereotype);
- }
- }
-
- }
-
- return new LinkedList<Stereotype>(result);
- }
-
- public static boolean isSubStereotype(Stereotype parentStereotype, Stereotype childStereotype) {
- if (parentStereotype == childStereotype) {
- return true;
- }
- return childStereotype.getGenerals().contains(parentStereotype); // TODO : This is probably not recursive...
- }
-
- /**
- * Finds recursively all stereotypes contained in this profile
- *
- * @param profile
- *
- * @param result
- */
- public static void findAllStereotypes(Profile profile, List<Stereotype> result) {
- for (Stereotype stereotype : profile.getOwnedStereotypes()) {
- result.add(stereotype);
- }
- for (Package subPackage : profile.getNestedPackages()) {
- if (subPackage instanceof Profile) {
- Profile subProfile = (Profile) subPackage;
- findAllStereotypes(subProfile, result);
- }
- }
- }
-
- private static EPackage umlMetamodel = UMLPackage.eINSTANCE;
-
- public static List<EClass> getAllExtendedMetaclasses(Stereotype stereotype, boolean concreteClassesOnly) {
- List<Class> extendedMetaclasses = stereotype.getAllExtendedMetaclasses();
- Set<EClass> allMetaclasses = new LinkedHashSet<EClass>();
- for (Class extendedMetaclass : extendedMetaclasses) {
- EClass UMLEClass = findEClass(extendedMetaclass);
- allMetaclasses.addAll(EMFHelper.getSubclassesOf(UMLEClass, concreteClassesOnly));
- }
-
- return new LinkedList<EClass>(allMetaclasses);
- }
-
- private static EClass findEClass(Class metaclass) {
- for (EClassifier classifier : umlMetamodel.getEClassifiers()) {
- if (classifier instanceof EClass) {
- if (classifier.getName().equals(metaclass.getName())) {
- return (EClass) classifier;
- }
- }
- }
- return null;
- }
-
- /**
- * Searchs the association containing a member end matching the given name
- * in the given stereotype, and returns it if it is typed by a stereotype
- *
- * TODO : This method can probably be used in a single specific case and should probably not be here
- *
- * @param stereotype
- * @param associationName
- * @return
- */
- public static Property findStereotypedProperty(Stereotype stereotype, String associationName) {
- // associations loop
- associations: for (Association association : stereotype.getAssociations()) {
- // memberEnds loop
- for (Property memberEnd : association.getMemberEnds()) {
- if (memberEnd.getType() == stereotype) {
- // oppositeEnds loop
- for (Property oppositeEnd : association.getMemberEnds()) {
- if (oppositeEnd != memberEnd && oppositeEnd.getName().equals(associationName) && !association.getOwnedEnds().contains(oppositeEnd)) {
- if (oppositeEnd.getType() instanceof Stereotype) {
- return oppositeEnd;
- }
- break associations; // break the three loops
- }
- }
- }
- }
- }
-
- // FIXME : Do we really need to browse associations first ?
-
- // Search for properties which are not associations
- for (Property property : stereotype.getAllAttributes()) {
- if (property.getName().equals(associationName)) {
- if (property.getType() instanceof Stereotype) {
- return property;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Finds the profile application, if any, in the context of an {@code element} that supplies the given
- * stereotype Ecore definition.
- *
- * @param element
- * a model element
- * @param stereotypeDefinition
- * the Ecore definition of a stereotype
- *
- * @return the providing profile application, or {@code null} if none can be determined
- */
- public static ProfileApplication getProfileApplication(Element element, EClass stereotypeDefinition) {
- ProfileApplication result = null;
-
- NamedElement umlDefinition = getNamedElement(stereotypeDefinition, element);
- if (umlDefinition != null) {
- Profile profile = getContainingProfile(umlDefinition);
- if (profile != null) {
- result = getApplicationOf(profile, element);
- }
- }
-
- return result;
- }
-
- static Profile getContainingProfile(NamedElement umlDefinition) {
- Profile result = null;
-
- for (Package package_ = umlDefinition.getNearestPackage(); package_ != null; package_ = package_.getNestingPackage()) {
- if (package_ instanceof Profile) {
- result = (Profile) package_;
- break;
- }
- }
-
- return result;
- }
-
- static ProfileApplication getApplicationOf(Profile profile, Element context) {
- ProfileApplication result = null;
-
- Package package_ = context.getNearestPackage();
- if (package_ != null) {
- result = package_.getProfileApplication(profile, true);
- }
-
- return result;
- }
-
- /**
- * Sort a list of profiles in dependency order, such that profiles that are used by other profiles sort ahead of the profiles that use them.
- * A profile uses another profile when it has classifiers that either:
- * <ul>
- * <li>specialize classifiers contained in the other profile (possibly in nested packages), or</li>
- * <li>have attributes typed by classifiers in the other profile, or</li>
- * <li>have operations with parameters typed by classifiers in the other profile</li>
- * </ul>
- *
- * @param profiles
- * a list of profiles to sort in place
- */
- public static void sortProfiles(List<Profile> profiles) {
- // Analyze profiles for inter-dependencies
- final SetMultimap<Profile, Profile> dependencies = computeProfileDependencies(profiles);
-
- // Expand to find transitive dependencies
- expand(dependencies);
-
- // Now sort by used profiles ahead of those that use them
- Collections.sort(profiles, new Comparator<Profile>() {
- public int compare(Profile o1, Profile o2) {
- return dependencies.get(o1).contains(o2) ? +1 : dependencies.get(o2).contains(o1) ? -1 : 0;
- }
- });
- }
-
- /**
- * Compute a mapping of profiles to the profiles that they depend on.
- *
- * @param profiles
- * profiles for which to compute dependencies
- * @return the dependency mapping (one-to-many)
- */
- private static SetMultimap<Profile, Profile> computeProfileDependencies(Collection<? extends Profile> profiles) {
- SetMultimap<Profile, Profile> result = HashMultimap.create();
- for (Profile next : profiles) {
- for (PackageableElement member : next.getPackagedElements()) {
- if (member instanceof Classifier) {
- // Look for supertype dependencies
- for (Classifier general : ((Classifier) member).allParents()) {
- addProfileContaining(general, next, result);
- }
- }
- if (member instanceof AttributeOwner) {
- // Look for attribute type dependencies
- for (Property property : ((AttributeOwner) member).getOwnedAttributes()) {
- addProfileContaining(property.getType(), next, result);
- }
- }
- if (member instanceof OperationOwner) {
- // Look for operation parameter type dependencies
- for (Operation operation : ((OperationOwner) member).getOwnedOperations()) {
- for (Parameter parameter : operation.getOwnedParameters()) {
- addProfileContaining(parameter.getType(), next, result);
- }
- }
- }
- }
-
- // Exclude self dependencies
- result.remove(next, next);
- }
-
- return result;
- }
-
- private static void addProfileContaining(PackageableElement element, Profile dependent, SetMultimap<Profile, Profile> dependencies) {
- if (element != null) {
- Package containingPackage = element.getNearestPackage();
- while ((containingPackage != null) && !(containingPackage instanceof Profile) && (containingPackage.getOwner() != null)) {
- containingPackage = containingPackage.getOwner().getNearestPackage();
- }
-
- if (containingPackage instanceof Profile) {
- dependencies.put(dependent, (Profile) containingPackage);
- }
- }
- }
-
- /**
- * Expands a profile dependency map to include all transitive dependencies in the mapping for each profile.
- *
- * @param dependencies
- * a dependency map to expand
- */
- private static void expand(SetMultimap<Profile, Profile> dependencies) {
- for (boolean changed = true; changed;) {
- changed = false;
-
- for (Profile next : new ArrayList<Profile>(dependencies.keySet())) {
- for (Profile dep : new ArrayList<Profile>(dependencies.get(next))) {
- Set<Profile> transitive = dependencies.get(dep);
-
- // Add the transitive dependencies to my own
- if ((transitive != null) && dependencies.putAll(next, transitive)) {
- changed = true;
- }
- }
- }
- }
- }
-}
+/***************************************************************************** + * Copyright (c) 2012, 2016 CEA LIST, Christian W. Damus, Esterel Technologies SAS 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: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - Handle dynamic profile applications in CDO + * Christian W. Damus - bug 399859 + * Christian W. Damus - bug 451613 + * Christian W. Damus - bug 474610 + * Calin Glitia (Esterel Technologies SAS) - bug 497699 + * + *****************************************************************************/ +package org.eclipse.papyrus.uml.tools.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +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.resource.URIConverter; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.papyrus.infra.emf.utils.EMFHelper; +import org.eclipse.uml2.common.util.UML2Util; +import org.eclipse.uml2.uml.Association; +import org.eclipse.uml2.uml.AttributeOwner; +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.Operation; +import org.eclipse.uml2.uml.OperationOwner; +import org.eclipse.uml2.uml.Package; +import org.eclipse.uml2.uml.PackageableElement; +import org.eclipse.uml2.uml.Parameter; +import org.eclipse.uml2.uml.Profile; +import org.eclipse.uml2.uml.ProfileApplication; +import org.eclipse.uml2.uml.Property; +import org.eclipse.uml2.uml.Stereotype; +import org.eclipse.uml2.uml.UMLPackage; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; + +/** + * A class containing static utility method regarding UML profiles + * + * @author Camille Letavernier + * + */ +public class ProfileUtil extends org.eclipse.uml2.uml.util.UMLUtil { + + + /** + * Checks if the profile applied has been changed since last application (definition does not match. + * + * @param _package + * on which the profile is applied + * @param _profile + * the applied profile + * @return true if the profile has changed + */ + public static boolean isDirty(Package _package, Profile _profile) { + boolean isDirty = false; + if (_profile == null || _profile.eResource() == null || _package == null || _package.eResource() == null) { + return false; + } + + // Retrieve model resourceSet + ResourceSet pkge_resourceSet = _package.eResource().getResourceSet(); + + if (pkge_resourceSet != null) { + + // Retrieve profile resource + URI prof_URI = _profile.eResource().getURI(); + Resource modelResource = pkge_resourceSet.getResource(prof_URI, true); + + if (modelResource != null && modelResource.getContents().get(0) instanceof Profile) { + + // ckeck applied profile application definition vs profile definition referenced in file + Profile profileInFile = (Profile) (modelResource.getContents().get(0)); + ProfileApplication profileApplication = _package.getProfileApplication(_profile, true); + if (profileApplication != null) { + EPackage appliedProfileDefinition = profileApplication.getAppliedDefinition(); + EPackage fileProfileDefinition = null; + + // Check profiles qualified names to ensure the correct profiles are compared + String appliedProfileName = _profile.getQualifiedName(); + String fileProfileName = profileInFile.getQualifiedName(); + if (!appliedProfileName.equals(fileProfileName)) { + + // The profile must be a subprofile + Iterator<Profile> it = PackageUtil.getSubProfiles(profileInFile).iterator(); + while (it.hasNext()) { + Profile current = it.next(); + fileProfileName = current.getQualifiedName(); + if (fileProfileName.equals(appliedProfileName)) { + profileInFile = current; + } + } + } + + fileProfileDefinition = profileInFile.getDefinition(); + + // don't just test that the EPackage definitions are the + // same object because in the CDO context they are not, even + // though they are "the same package". Comparing the URIs + // should suffice + final URIConverter converter = pkge_resourceSet.getURIConverter(); + if ((appliedProfileDefinition == null) + || (fileProfileDefinition == null) + || !UML2Util.safeEquals( + converter.normalize(EcoreUtil.getURI(appliedProfileDefinition)), + converter.normalize(EcoreUtil.getURI(fileProfileDefinition)))) { + + isDirty = true; + } + } + + } + } + + + + return isDirty; + } + + public static List<Stereotype> findAllSubStereotypes(Stereotype parentStereotype, Package umlPackage, boolean concreteOnly) { + Collection<Stereotype> result = new LinkedHashSet<Stereotype>(); + + for (Profile profile : umlPackage.getAllAppliedProfiles()) { + List<Stereotype> allStereotypes = new LinkedList<Stereotype>(); + findAllStereotypes(profile, allStereotypes); + for (Stereotype stereotype : allStereotypes) { + if (concreteOnly && stereotype.isAbstract()) { + continue; // Skip abstract stereotypes + } + + if (isSubStereotype(parentStereotype, stereotype)) { + result.add(stereotype); + } + } + + } + + return new LinkedList<Stereotype>(result); + } + + public static boolean isSubStereotype(Stereotype parentStereotype, Stereotype childStereotype) { + if (parentStereotype == childStereotype) { + return true; + } + return childStereotype.getGenerals().contains(parentStereotype); // TODO : This is probably not recursive... + } + + /** + * Finds recursively all stereotypes contained in this profile + * + * @param profile + * + * @param result + */ + public static void findAllStereotypes(Profile profile, List<Stereotype> result) { + for (Stereotype stereotype : profile.getOwnedStereotypes()) { + result.add(stereotype); + } + for (Package subPackage : profile.getNestedPackages()) { + if (subPackage instanceof Profile) { + Profile subProfile = (Profile) subPackage; + findAllStereotypes(subProfile, result); + } + } + } + + private static EPackage umlMetamodel = UMLPackage.eINSTANCE; + + public static List<EClass> getAllExtendedMetaclasses(Stereotype stereotype, boolean concreteClassesOnly) { + List<Class> extendedMetaclasses = stereotype.getAllExtendedMetaclasses(); + Set<EClass> allMetaclasses = new LinkedHashSet<EClass>(); + for (Class extendedMetaclass : extendedMetaclasses) { + EClass UMLEClass = findEClass(extendedMetaclass); + allMetaclasses.addAll(EMFHelper.getSubclassesOf(UMLEClass, concreteClassesOnly)); + } + + return new LinkedList<EClass>(allMetaclasses); + } + + private static EClass findEClass(Class metaclass) { + for (EClassifier classifier : umlMetamodel.getEClassifiers()) { + if (classifier instanceof EClass) { + if (classifier.getName().equals(metaclass.getName())) { + return (EClass) classifier; + } + } + } + return null; + } + + /** + * Searchs the association containing a member end matching the given name + * in the given stereotype, and returns it if it is typed by a stereotype + * + * TODO : This method can probably be used in a single specific case and should probably not be here + * + * @param stereotype + * @param associationName + * @return + */ + public static Property findStereotypedProperty(Stereotype stereotype, String associationName) { + // associations loop + associations: for (Association association : stereotype.getAssociations()) { + // memberEnds loop + for (Property memberEnd : association.getMemberEnds()) { + if (memberEnd.getType() == stereotype) { + // oppositeEnds loop + for (Property oppositeEnd : association.getMemberEnds()) { + if (oppositeEnd != memberEnd && oppositeEnd.getName().equals(associationName) && !association.getOwnedEnds().contains(oppositeEnd)) { + if (oppositeEnd.getType() instanceof Stereotype) { + return oppositeEnd; + } + break associations; // break the three loops + } + } + } + } + } + + // FIXME : Do we really need to browse associations first ? + + // Search for properties which are not associations + for (Property property : stereotype.getAllAttributes()) { + if (property.getName().equals(associationName)) { + if (property.getType() instanceof Stereotype) { + return property; + } + } + } + + return null; + } + + /** + * Finds the profile application, if any, in the context of an {@code element} that supplies the given + * stereotype Ecore definition. + * + * @param element + * a model element + * @param stereotypeDefinition + * the Ecore definition of a stereotype + * + * @return the providing profile application, or {@code null} if none can be determined + */ + public static ProfileApplication getProfileApplication(Element element, EClass stereotypeDefinition) { + ProfileApplication result = null; + + NamedElement umlDefinition = getNamedElement(stereotypeDefinition, element); + if (umlDefinition != null) { + Profile profile = getContainingProfile(umlDefinition); + if (profile != null) { + result = getApplicationOf(profile, element); + } + } + + return result; + } + + static Profile getContainingProfile(NamedElement umlDefinition) { + Profile result = null; + + for (Package package_ = umlDefinition.getNearestPackage(); package_ != null; package_ = package_.getNestingPackage()) { + if (package_ instanceof Profile) { + result = (Profile) package_; + break; + } + } + + return result; + } + + static ProfileApplication getApplicationOf(Profile profile, Element context) { + ProfileApplication result = null; + + Package package_ = context.getNearestPackage(); + if (package_ != null) { + result = package_.getProfileApplication(profile, true); + } + + return result; + } + + /** + * Sort a list of profiles in dependency order, such that profiles that are used by other profiles sort ahead of the profiles that use them. + * A profile uses another profile when it has classifiers that either: + * <ul> + * <li>specialize classifiers contained in the other profile (possibly in nested packages), or</li> + * <li>have attributes typed by classifiers in the other profile, or</li> + * <li>have operations with parameters typed by classifiers in the other profile</li> + * </ul> + * + * @param profiles + * a list of profiles to sort in place + */ + public static void sortProfiles(List<Profile> profiles) { + // Analyze profiles for inter-dependencies + final SetMultimap<Profile, Profile> dependencies = computeProfileDependencies(profiles); + + // Expand to find transitive dependencies + expand(dependencies); + + // Now sort by used profiles ahead of those that use them + Collections.sort(profiles, new Comparator<Profile>() { + public int compare(Profile o1, Profile o2) { + return dependencies.get(o1).contains(o2) ? +1 : dependencies.get(o2).contains(o1) ? -1 : 0; + } + }); + } + + /** + * Compute a mapping of profiles to the profiles that they depend on. + * + * @param profiles + * profiles for which to compute dependencies + * @return the dependency mapping (one-to-many) + */ + private static SetMultimap<Profile, Profile> computeProfileDependencies(Collection<? extends Profile> profiles) { + SetMultimap<Profile, Profile> result = HashMultimap.create(); + for (Profile next : profiles) { + for (PackageableElement member : next.getPackagedElements()) { + if (member instanceof Classifier) { + // Look for supertype dependencies + for (Classifier general : ((Classifier) member).allParents()) { + addProfileContaining(general, next, result); + } + } + if (member instanceof AttributeOwner) { + // Look for attribute type dependencies + for (Property property : ((AttributeOwner) member).getOwnedAttributes()) { + addProfileContaining(property.getType(), next, result); + } + } + if (member instanceof OperationOwner) { + // Look for operation parameter type dependencies + for (Operation operation : ((OperationOwner) member).getOwnedOperations()) { + for (Parameter parameter : operation.getOwnedParameters()) { + addProfileContaining(parameter.getType(), next, result); + } + } + } + } + + // Exclude self dependencies + result.remove(next, next); + } + + return result; + } + + private static void addProfileContaining(PackageableElement element, Profile dependent, SetMultimap<Profile, Profile> dependencies) { + if (element != null) { + Package containingPackage = element.getNearestPackage(); + while ((containingPackage != null) && !(containingPackage instanceof Profile) && (containingPackage.getOwner() != null)) { + containingPackage = containingPackage.getOwner().getNearestPackage(); + } + + if (containingPackage instanceof Profile) { + dependencies.put(dependent, (Profile) containingPackage); + } + } + } + + /** + * Expands a profile dependency map to include all transitive dependencies in the mapping for each profile. + * + * @param dependencies + * a dependency map to expand + */ + private static void expand(SetMultimap<Profile, Profile> dependencies) { + for (boolean changed = true; changed;) { + changed = false; + + for (Profile next : new ArrayList<Profile>(dependencies.keySet())) { + for (Profile dep : new ArrayList<Profile>(dependencies.get(next))) { + Set<Profile> transitive = dependencies.get(dep); + + // Add the transitive dependencies to my own + if ((transitive != null) && dependencies.putAll(next, transitive)) { + changed = true; + } + } + } + } + } +} |