From b5a080220a384bc136b1b39dac98354dc3d00ddf Mon Sep 17 00:00:00 2001 From: Christian W. Damus Date: Mon, 17 Nov 2014 18:59:12 -0500 Subject: 451613: ClassCastException when updating hierarchical profile with datatype https://bugs.eclipse.org/bugs/show_bug.cgi?id=451613 Define profiles in dependency order to ensure that their Ecore definitions reference each other precisely by Ecore definition, not by UML definition. Conflicts: plugins/uml/tools/org.eclipse.papyrus.uml.tools.utils/src/org/eclipse/papyrus/uml/tools/utils/ProfileUtil.java tests/junit/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.profile.tests/META-INF/MANIFEST.MF --- .../custom/commands/DefineProfileCommand.java | 36 +- .../papyrus/uml/tools/utils/ProfileUtil.java | 584 ++++++++++++--------- 2 files changed, 372 insertions(+), 248 deletions(-) (limited to 'plugins') diff --git a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.profile/custom-src/org/eclipse/papyrus/uml/diagram/profile/custom/commands/DefineProfileCommand.java b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.profile/custom-src/org/eclipse/papyrus/uml/diagram/profile/custom/commands/DefineProfileCommand.java index 83cb47f07da..1bd908f8a91 100644 --- a/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.profile/custom-src/org/eclipse/papyrus/uml/diagram/profile/custom/commands/DefineProfileCommand.java +++ b/plugins/uml/diagram/org.eclipse.papyrus.uml.diagram.profile/custom-src/org/eclipse/papyrus/uml/diagram/profile/custom/commands/DefineProfileCommand.java @@ -1,6 +1,5 @@ /***************************************************************************** - * Copyright (c) 2010 CEA LIST. - * + * Copyright (c) 2010, 2014 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 @@ -9,14 +8,14 @@ * * Contributors: * Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Initial API and implementation + * Christian W. Damus - bug 451613 * *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.profile.custom.commands; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -31,6 +30,7 @@ import org.eclipse.emf.common.notify.AdapterFactory; import org.eclipse.emf.common.util.BasicDiagnostic; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.common.util.DiagnosticChain; +import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; @@ -46,6 +46,8 @@ import org.eclipse.papyrus.infra.emf.utils.EMFHelper; import org.eclipse.papyrus.uml.profile.Activator; import org.eclipse.papyrus.uml.tools.profile.definition.PapyrusDefinitionAnnotation; import org.eclipse.papyrus.uml.tools.profile.definition.ProfileRedefinition; +import org.eclipse.papyrus.uml.tools.utils.ProfileUtil; +import org.eclipse.uml2.common.util.UML2Util; import org.eclipse.uml2.uml.Package; import org.eclipse.uml2.uml.Profile; import org.eclipse.uml2.uml.util.UMLUtil; @@ -125,19 +127,25 @@ public class DefineProfileCommand extends AbstractTransactionalCommand { options.put(UML2EcoreConverter.OPTION__UNTYPED_PROPERTIES, UMLUtil.OPTION__PROCESS); // Closer to the UML semantics of untyped properties - List result = new LinkedList(); + List toDefine = new ArrayList(); + for (TreeIterator all = UML2Util.getAllContents(thePackage, true, false); all.hasNext();) { + EObject next = all.next(); + if (next instanceof Profile) { + toDefine.add((Profile) next); + } else if (!(next instanceof Package)) { + all.prune(); + } + } - // we want to define - if (thePackage instanceof Profile) { - EPackage profileDefinition = ((Profile) thePackage).define(options, null, null); - result.add(profileDefinition); + if (toDefine.size() > 1) { + // Sort the profiles in dependency order: used profiles ahead of the profiles that use them. This ensures that + // Ecore definitions are available for reference in the 4.1.0-style annotations instead of 4.0.0-style + ProfileUtil.sortProfiles(toDefine); } - Iterator it = thePackage.getNestedPackages().iterator(); - while (it.hasNext()) { - Package p = it.next(); - List profileDefinitions = defineProfiles(p, saveConstraintInDef); - result.addAll(profileDefinitions); + List result = new ArrayList(toDefine.size()); + for (Profile next : toDefine) { + result.add(next.define(options, null, null)); } return result; 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 9c4502715e5..ba66232fd79 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,234 +1,350 @@ -/***************************************************************************** - * Copyright (c) 2012, 2013 CEA LIST. - * - * 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 - * - *****************************************************************************/ -package org.eclipse.papyrus.uml.tools.utils; - -import java.util.Collection; -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.papyrus.infra.emf.utils.EMFHelper; -import org.eclipse.uml2.common.util.UML2Util; -import org.eclipse.uml2.uml.Association; -import org.eclipse.uml2.uml.Class; -import org.eclipse.uml2.uml.Package; -import org.eclipse.uml2.uml.Profile; -import org.eclipse.uml2.uml.Property; -import org.eclipse.uml2.uml.Stereotype; -import org.eclipse.uml2.uml.UMLPackage; - -/** - * A class containing static utility method regarding UML profiles - * - * @author Camille Letavernier - * - */ -public class ProfileUtil { - - - /** - * 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)); - - if (_package.getProfileApplication(_profile) != null) { - EPackage appliedProfileDefinition = _package.getProfileApplication(_profile).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 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 NSURIs - // should suffice - if ((appliedProfileDefinition == null) - || (fileProfileDefinition == null) - || !UML2Util.safeEquals( - appliedProfileDefinition.getNsURI(), - fileProfileDefinition.getNsURI())) { - - isDirty = true; - } - } - - } - } - - - - return isDirty; - } - - public static List findAllSubStereotypes(Stereotype parentStereotype, Package umlPackage, boolean concreteOnly) { - Collection result = new LinkedHashSet(); - - for (Profile profile : umlPackage.getAllAppliedProfiles()) { - List allStereotypes = new LinkedList(); - 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(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 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 getAllExtendedMetaclasses(Stereotype stereotype, boolean concreteClassesOnly) { - List extendedMetaclasses = stereotype.getAllExtendedMetaclasses(); - Set allMetaclasses = new LinkedHashSet(); - for (Class extendedMetaclass : extendedMetaclasses) { - EClass UMLEClass = findEClass(extendedMetaclass); - allMetaclasses.addAll(EMFHelper.getSubclassesOf(UMLEClass, concreteClassesOnly)); - } - - return new LinkedList(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; - } -} +/***************************************************************************** + * Copyright (c) 2012, 2014 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 451613 + * + *****************************************************************************/ +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.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.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.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 { + + + /** + * 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)); + + if (_package.getProfileApplication(_profile) != null) { + EPackage appliedProfileDefinition = _package.getProfileApplication(_profile).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 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 NSURIs + // should suffice + if ((appliedProfileDefinition == null) + || (fileProfileDefinition == null) + || !UML2Util.safeEquals( + appliedProfileDefinition.getNsURI(), + fileProfileDefinition.getNsURI())) { + + isDirty = true; + } + } + + } + } + + + + return isDirty; + } + + public static List findAllSubStereotypes(Stereotype parentStereotype, Package umlPackage, boolean concreteOnly) { + Collection result = new LinkedHashSet(); + + for (Profile profile : umlPackage.getAllAppliedProfiles()) { + List allStereotypes = new LinkedList(); + 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(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 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 getAllExtendedMetaclasses(Stereotype stereotype, boolean concreteClassesOnly) { + List extendedMetaclasses = stereotype.getAllExtendedMetaclasses(); + Set allMetaclasses = new LinkedHashSet(); + for (Class extendedMetaclass : extendedMetaclasses) { + EClass UMLEClass = findEClass(extendedMetaclass); + allMetaclasses.addAll(EMFHelper.getSubclassesOf(UMLEClass, concreteClassesOnly)); + } + + return new LinkedList(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; + } + + /** + * 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: + *
    + *
  • specialize classifiers contained in the other profile (possibly in nested packages), or
  • + *
  • have attributes typed by classifiers in the other profile, or
  • + *
  • have operations with parameters typed by classifiers in the other profile
  • + *
+ * + * @param profiles + * a list of profiles to sort in place + */ + public static void sortProfiles(List profiles) { + // Analyze profiles for inter-dependencies + final SetMultimap 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() { + 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 computeProfileDependencies(Collection profiles) { + SetMultimap 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 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 dependencies) { + for (boolean changed = true; changed;) { + changed = false; + + for (Profile next : new ArrayList(dependencies.keySet())) { + for (Profile dep : new ArrayList(dependencies.get(next))) { + Set transitive = dependencies.get(dep); + + // Add the transitive dependencies to my own + if ((transitive != null) && dependencies.putAll(next, transitive)) { + changed = true; + } + } + } + } + } +} -- cgit v1.2.3