diff options
author | Christian W. Damus | 2014-04-09 22:53:30 +0000 |
---|---|---|
committer | Christian W. Damus | 2014-04-15 17:49:12 +0000 |
commit | 5ebf7f2fb8d1734fa7e18638df56ff8ce07be53f (patch) | |
tree | 1177a2e7a5023af2f8c760d996da57ddc6155c13 /plugins/uml | |
parent | a69ae2b062345500a8aefc6c90d5795ecda3302b (diff) | |
download | org.eclipse.papyrus-5ebf7f2fb8d1734fa7e18638df56ff8ce07be53f.tar.gz org.eclipse.papyrus-5ebf7f2fb8d1734fa7e18638df56ff8ce07be53f.tar.xz org.eclipse.papyrus-5ebf7f2fb8d1734fa7e18638df56ff8ce07be53f.zip |
431953: Stereotype garbage left in .uml file after removing profile (crash reason?)
https://bugs.eclipse.org/bugs/show_bug.cgi?id=431953
Implement a model-set snippet that detects and repairs (with user interaction) stereotype applications that either are not described by any profile application in the context of the a resource's root element when a resource is loaded.
Includes refactoring of the UI and back-end of the Switch Profiles functionality.
Cases of broken stereotypes include:
- instances of EClasses from a different version of the profile's Ecore definition than what is currently applied
- instances of EClasses from a profile that is not applied at all
- instances of EClasses from a profile that may be applied but which is unresolved (no longer exists at its former location)
Diffstat (limited to 'plugins/uml')
15 files changed, 2113 insertions, 144 deletions
diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/META-INF/MANIFEST.MF b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/META-INF/MANIFEST.MF index 1c597de351a..55c14b8b9b8 100644 --- a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/META-INF/MANIFEST.MF +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/META-INF/MANIFEST.MF @@ -2,6 +2,7 @@ Manifest-Version: 1.0 Export-Package: org.eclipse.papyrus.uml.modelrepair,
org.eclipse.papyrus.uml.modelrepair.handler,
org.eclipse.papyrus.uml.modelrepair.internal.participants;x-internal:=true,
+ org.eclipse.papyrus.uml.modelrepair.internal.stereotypes;x-internal:=true,
org.eclipse.papyrus.uml.modelrepair.ui,
org.eclipse.papyrus.uml.modelrepair.ui.providers
Require-Bundle: org.eclipse.ui,
diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/plugin.xml b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/plugin.xml index bd9c361fecd..1445b6a5011 100644 --- a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/plugin.xml +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/plugin.xml @@ -110,5 +110,12 @@ class="org.eclipse.papyrus.uml.modelrepair.internal.participants.StereotypeApplicationRepairParticipant">
</replaceParticipant>
</extension>
+ <extension
+ point="org.eclipse.papyrus.infra.core.model">
+ <modelSetSnippet
+ classname="org.eclipse.papyrus.uml.modelrepair.internal.stereotypes.StereotypeApplicationRepairSnippet"
+ description="Initiates repair of zombie stereotype applications on load of a UML resource.">
+ </modelSetSnippet>
+ </extension>
</plugin>
diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/participants/StereotypeApplicationRepairParticipant.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/participants/StereotypeApplicationRepairParticipant.java index eaca8e89c36..5173794b52e 100644 --- a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/participants/StereotypeApplicationRepairParticipant.java +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/participants/StereotypeApplicationRepairParticipant.java @@ -15,6 +15,7 @@ package org.eclipse.papyrus.uml.modelrepair.internal.participants; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubMonitor; @@ -23,8 +24,12 @@ import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.common.util.DiagnosticChain; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; @@ -34,6 +39,11 @@ 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.emf.ecore.util.ExtendedMetaData; +import org.eclipse.emf.ecore.util.FeatureMap; +import org.eclipse.emf.ecore.xmi.XMLResource; +import org.eclipse.emf.ecore.xml.type.AnyType; +import org.eclipse.emf.ecore.xml.type.XMLTypePackage; import org.eclipse.papyrus.infra.emf.resource.IDependencyReplacementParticipant; import org.eclipse.papyrus.infra.emf.resource.Replacement; import org.eclipse.papyrus.infra.emf.utils.EMFHelper; @@ -47,6 +57,7 @@ import org.eclipse.uml2.uml.Profile; import org.eclipse.uml2.uml.ProfileApplication; import org.eclipse.uml2.uml.UMLPackage; import org.eclipse.uml2.uml.internal.operations.PackageOperations; +import org.eclipse.uml2.uml.util.UMLUtil; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -123,8 +134,6 @@ public class StereotypeApplicationRepairParticipant extends PackageOperations im } } - EcoreUtil.Copier copier = new StereotypeApplicationRepairCopier(newProfile, diagnostics); - List<EObject> oldStereotypeApplications = Lists.newArrayList(); for(TreeIterator<EObject> iter = getAllContents(applyingPackage, true, false); iter.hasNext();) { EObject element = iter.next(); @@ -137,14 +146,6 @@ public class StereotypeApplicationRepairParticipant extends PackageOperations im // Looks like a stereotype application. Do we need to rebuild it? if(owner.eClass().getEPackage() == oldDefinition) { oldStereotypeApplications.add(owner); - EObject newInstance = copier.copy(owner); - if((newInstance != null) && (newInstance != owner)) { - // Depends how we copied the stereotype instance (by applying again or not), - // it may not be attached, yet - if(newInstance.eResource() == null) { - EcoreUtil.replace(owner, newInstance); - } - } } } } @@ -154,9 +155,7 @@ public class StereotypeApplicationRepairParticipant extends PackageOperations im } } - copier.copyReferences(); - - UML2Util.destroyAll(oldStereotypeApplications); + createStereotypeApplicationMigrator(newProfile, diagnostics).migrate(oldStereotypeApplications); if(!newProfile.getOwnedExtensions(true).isEmpty()) { // Ensure that required stereotypes of the new profile are applied @@ -197,14 +196,48 @@ public class StereotypeApplicationRepairParticipant extends PackageOperations im return result; } + public static StereotypeApplicationMigrator createStereotypeApplicationMigrator(Profile profile, DiagnosticChain diagnostics) { + return new StereotypeApplicationMigrator(profile, diagnostics); + } + // // Nested types // + public static class StereotypeApplicationMigrator { + + private StereotypeApplicationRepairCopier copier; + + StereotypeApplicationMigrator(Profile profile, DiagnosticChain diagnostics) { + copier = new StereotypeApplicationRepairCopier(profile, diagnostics); + } + + public void migrate(Collection<? extends EObject> stereotypeApplications) { + for(EObject next : stereotypeApplications) { + EObject newInstance = copier.copy(next); + if((newInstance != null) && (newInstance != next)) { + // Depends how we copied the stereotype instance (by applying again or not), + // it may not be attached, yet + if(newInstance.eResource() == null) { + EcoreUtil.replace(next, newInstance); + } + } + } + + copier.copyReferences(); + + UML2Util.destroyAll(stereotypeApplications); + + copier.clear(); + } + } + protected static class StereotypeApplicationRepairCopier extends StereotypeApplicationCopier { private static final long serialVersionUID = 1L; + private final Pattern whitespace = Pattern.compile("\\s+"); //$NON-NLS-1$ + private final DiagnosticChain diagnostics; private EObject copying; @@ -222,7 +255,7 @@ public class StereotypeApplicationRepairParticipant extends PackageOperations im @Override public EObject copy(EObject eObject) { final EObject previousCopying = copying; - + try { copying = eObject; return super.copy(eObject); @@ -230,19 +263,275 @@ public class StereotypeApplicationRepairParticipant extends PackageOperations im copying = previousCopying; } } - + + @Override + protected NamedElement getNamedElement(ENamedElement element) { + if(element instanceof EClassifier) { + EClassifier classifier = (EClassifier)element; + // Handle case of unrecognized schema + if(isUnrecognizedSchema(classifier.getEPackage()) && (profile.getDefinition() != null)) { + // It's unrecognized content. Force look-up in the profile chosen by the user + EClassifier force = profile.getDefinition().getEClassifier(classifier.getName()); + if(force != null) { + element = force; + } + } + } else if(element instanceof EStructuralFeature) { + EStructuralFeature feature = (EStructuralFeature)element; + // Handle case of unrecognized schema + if(isUnrecognizedSchema(feature.getEContainingClass().getEPackage()) && (profile.getDefinition() != null)) { + EClassifier classifier = profile.getDefinition().getEClassifier(feature.getEContainingClass().getName()); + if(classifier instanceof EClass) { + EStructuralFeature force = ((EClass)classifier).getEStructuralFeature(element.getName()); + if(force != null) { + element = force; + } + } + } + } + + return super.getNamedElement(element); + } + + protected boolean isUnrecognizedSchema(EPackage ePackage) { + boolean result; + + if(copying != null) { + result = getExtendedMetadata(copying).demandedPackages().contains(ePackage); + } else { + // Simple heuristic: unknown-schema packages don't have names, but profile-defined packages always do + result = (ePackage.getName() == null); + } + + return result; + } + + protected ExtendedMetaData getExtendedMetadata(EObject context) { + ExtendedMetaData result = ExtendedMetaData.INSTANCE; + + Resource resource = context.eResource(); + if(resource instanceof XMLResource) { + Object option = ((XMLResource)resource).getDefaultSaveOptions().get(XMLResource.OPTION_EXTENDED_META_DATA); + if(option instanceof ExtendedMetaData) { + result = (ExtendedMetaData)option; + } + } + + return result; + } + + @Override + protected void copyAttribute(EAttribute eAttribute, EObject eObject, EObject copyEObject) { + if(copyEObject == null) { + // We couldn't find the corresponding stereotype/class/datatype in the profile, so it is dropped and we can't copy any properties + return; + } + + if(eAttribute == XMLTypePackage.Literals.ANY_TYPE__ANY_ATTRIBUTE) { + // The 'anyAttribute' feature-map only appears in unknown schema content + copyUnrecognizedContentAnyAttribute(eAttribute, eObject, copyEObject); + } else if(eAttribute == XMLTypePackage.Literals.ANY_TYPE__MIXED) { + // UML stereotype applications will not have arbitrarily mixed content, but nested objects (further AnyTypes) + // and elements for multi-valued attributes will appear in this feature map + copyUnrecognizedContentMixed(eAttribute, eObject, copyEObject); + } else { + super.copyAttribute(eAttribute, eObject, copyEObject); + } + } + + protected void copyUnrecognizedContentAnyAttribute(EAttribute anyAttribute, EObject eObject, EObject copyEObject) { + FeatureMap featureMap = (FeatureMap)eObject.eGet(anyAttribute); + for(FeatureMap.Entry next : featureMap) { + EStructuralFeature f = next.getEStructuralFeature(); + + EStructuralFeature copyFeature = copyEObject.eClass().getEStructuralFeature(f.getName()); + if(copyFeature instanceof EReference) { + // values in the XMI will be IDREFs or HREFs + String refs = String.valueOf(next.getValue()); + for(String ref : whitespace.split(refs)) { + EObject referenced = resolveRef(eObject, ref); + if(referenced == null) { + String propertyName = getQualifiedName(UMLUtil.getNamedElement(copyFeature, eObject)); + handleException(new IllegalStateException(String.format("Unresolved reference in stereotype property %s: %s", propertyName, ref))); //$NON-NLS-1$ + } else if(!copyFeature.getEType().isInstance(referenced)) { + String propertyName = getQualifiedName(UMLUtil.getNamedElement(copyFeature, eObject)); + handleException(new IllegalStateException(String.format("Attempt to reference object of type %s in stereotype property %s", UML2EcoreConverter.getOriginalName(referenced.eClass()), propertyName))); //$NON-NLS-1$ + } else { + eAdd(copyEObject, copyFeature, referenced); + } + } + } else if(copyFeature instanceof EAttribute) { + // values in the XMI will be string serializations of data types + EDataType dataType = ((EAttribute)copyFeature).getEAttributeType(); + + try { + Object value = EcoreUtil.createFromString(dataType, String.valueOf(next.getValue())); + eAdd(copyEObject, copyFeature, value); + } catch (Exception e) { + handleException(e); + } + } else { + // feature not matched. Because this is unknown schema, the Ecore feature's actual qualified name is useless + // (it's something like '{EPackage}::DocumentRoot::foo') + String qualifiedName = String.format("%s::%s", UMLUtil.getNamedElement(copyEObject.eClass(), eObject).getName(), f.getName()); + handleException(new IllegalStateException(String.format("Definition for property '%s' not found in profile '%s'", qualifiedName, getQualifiedName(profile)))); //$NON-NLS-1$ + } + } + } + + protected void eAdd(EObject owner, EStructuralFeature feature, Object value) { + if(feature.isChangeable() && !feature.isDerived()) { + if(feature.isMany()) { + @SuppressWarnings("unchecked") + Collection<Object> list = (Collection<Object>)owner.eGet(feature); + list.add(value); + } else { + owner.eSet(feature, value); + } + } + } + + protected EObject resolveRef(EObject anyType, String ref) { + Resource baseResource = anyType.eResource(); + + URI uri; + if(ref.contains("#")) { + // HREF case + uri = baseResource.getURI().resolve(URI.createURI(ref)); + } else { + // IDREF case + uri = baseResource.getURI().appendFragment(ref); + } + + return baseResource.getResourceSet().getEObject(uri, true); + } + + protected void copyUnrecognizedContentMixed(EAttribute mixed, EObject eObject, EObject copyEObject) { + FeatureMap featureMap = (FeatureMap)eObject.eGet(mixed); + for(FeatureMap.Entry next : featureMap) { + EStructuralFeature f = next.getEStructuralFeature(); + + // UML stereotypes do not use comments, arbitrary mixed text, processing instructions, etc. + if(f.getEContainingClass() != XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT) { + // The incoming thing is an element of some kind + if(f instanceof EReference) { + EObject anyType = (EObject)next.getValue(); + EStructuralFeature copyFeature = copyEObject.eClass().getEStructuralFeature(f.getName()); + if(copyFeature instanceof EAttribute) { + // Get the text value of the element, convert it to the appropriate data type, and set it into the attribute + try { + String text = getTextContent(anyType); + if(text != null) { + EDataType dataType = ((EAttribute)copyFeature).getEAttributeType(); + Object value = EcoreUtil.createFromString(dataType, text); + eAdd(copyEObject, copyFeature, value); + } + } catch (Exception e) { + handleException(e); + } + } else if(copyFeature instanceof EReference) { + EReference reference = (EReference)copyFeature; + if(!reference.isContainment()) { + // Get the HREF/IDREF from the element, resolve the referenced object, and set it into the reference + String refs = getTextContent(anyType); + if(refs != null) { + for(String ref : whitespace.split(refs)) { + EObject referenced = resolveRef(eObject, ref); + if(referenced == null) { + String propertyName = getQualifiedName(UMLUtil.getNamedElement(copyFeature, eObject)); + handleException(new IllegalStateException(String.format("Unresolved reference in stereotype property %s: %s", propertyName, ref))); //$NON-NLS-1$ + } else if(!copyFeature.getEType().isInstance(referenced)) { + String propertyName = getQualifiedName(UMLUtil.getNamedElement(copyFeature, eObject)); + handleException(new IllegalStateException(String.format("Attempt to reference object of type %s in stereotype property %s", UML2EcoreConverter.getOriginalName(referenced.eClass()), propertyName))); //$NON-NLS-1$ + } else { + eAdd(copyEObject, reference, referenced); + } + } + } + } else { + // Handle the contained object + EObject containedCopy = copy(anyType); + if(containedCopy != null) { + if(reference.getEReferenceType().isInstance(containedCopy)) { + eAdd(copyEObject, reference, containedCopy); + } else { + String propertyName = getQualifiedName(UMLUtil.getNamedElement(reference, eObject)); + handleException(new IllegalStateException(String.format("Attempt to contain object of type %s in stereotype property %s", UML2EcoreConverter.getOriginalName(containedCopy.eClass()), propertyName))); //$NON-NLS-1$ + } + } + } + } else { + // feature not matched. Because this is unknown schema, the Ecore feature's actual qualified name is useless + // (it's something like '{EPackage}::DocumentRoot::foo') + String qualifiedName = String.format("%s::%s", UMLUtil.getNamedElement(copyEObject.eClass(), eObject).getName(), f.getName()); + handleException(new IllegalStateException(String.format("Definition for property '%s' not found in profile '%s'", qualifiedName, getQualifiedName(profile)))); //$NON-NLS-1$ + } + } + } + } + } + + protected String getTextContent(EObject anyType) { + String result = null; + + Object value = anyType.eGet(XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__TEXT); + if(value instanceof String) { + result = (String)value; + } else if(value instanceof List<?>) { + List<?> list = (List<?>)value; + if(!list.isEmpty()) { + result = (String)list.get(0); + } + } + + return result; + } + @Override protected void copyReference(EReference eReference, EObject eObject, EObject copyEObject) { - final EObject previousCopying = copying; - - try { - copying = eObject; - super.copyReference(eReference, eObject, copyEObject); - } finally { - copying = previousCopying; + if(copyEObject != null) { + final EObject previousCopying = copying; + + try { + copying = eObject; + super.copyReference(eReference, eObject, copyEObject); + } finally { + copying = previousCopying; + } + } // Else we couldn't find the corresponding stereotype/class/datatype in the profile, so it is dropped and we can't copy any properties + } + + @Override + protected void copyProxyURI(EObject eObject, EObject copyEObject) { + if(copyEObject != null) { + super.copyProxyURI(eObject, copyEObject); + } // Else we couldn't find the corresponding stereotype/class/datatype in the profile, so it is dropped and we can't copy the proxy URI + } + + /** + * Override the EMF implementation to skip feature-maps, because we do not use them in UML Profiles. + */ + @Override + public void copyReferences() { + for(Map.Entry<EObject, EObject> entry : entrySet()) { + EObject eObject = entry.getKey(); + EObject copyEObject = entry.getValue(); + EClass eClass = eObject.eClass(); + + for(int i = 0, size = eClass.getFeatureCount(); i < size; ++i) { + EStructuralFeature eStructuralFeature = eClass.getEStructuralFeature(i); + if(eStructuralFeature.isChangeable() && !eStructuralFeature.isDerived()) { + if(eStructuralFeature instanceof EReference) { + EReference eReference = (EReference)eStructuralFeature; + if(!eReference.isContainment() && !eReference.isContainer()) { + copyReference(eReference, eObject, copyEObject); + } + } + } + } } } - + @Override protected EObject createCopy(EObject eObject) { try { @@ -276,7 +565,7 @@ public class StereotypeApplicationRepairParticipant extends PackageOperations im Object[] data = null; if(copying != null) { - Element base = (copying == null) ? null : getBaseElement(copying); + Element base = (copying == null) ? null : (copying instanceof AnyType) ? guessAnyTypeBaseElement(copying) : getBaseElement(copying); if(base != null) { data = new Object[]{ base }; } else { @@ -289,6 +578,36 @@ public class StereotypeApplicationRepairParticipant extends PackageOperations im super.handleException(exception); } } + + protected Element guessAnyTypeBaseElement(EObject anyType) { + Element result = null; + + // The base_Xyz extension end is always at most one, so it should be serialized as an IDREF + for(FeatureMap.Entry next : (FeatureMap)anyType.eGet(XMLTypePackage.Literals.ANY_TYPE__ANY_ATTRIBUTE)) { + if(next.getEStructuralFeature().getName().startsWith("base_")) { + EObject referenced = resolveRef(anyType, String.valueOf(next.getValue())); + if(referenced instanceof Element) { + result = (Element)referenced; + break; + } + } + } + + // But, if it's a cross-doc reference, it will be an HREF and thus probably an element + if(result == null) { + for(FeatureMap.Entry next : (FeatureMap)anyType.eGet(XMLTypePackage.Literals.ANY_TYPE__MIXED)) { + if((next.getEStructuralFeature() instanceof EReference) && next.getEStructuralFeature().getName().startsWith("base_")) { + EObject referenced = resolveRef(anyType, getTextContent((EObject)next.getValue())); + if(referenced instanceof Element) { + result = (Element)referenced; + break; + } + } + } + } + + return result; + } private static NamedElement findNamedElement(NamedElement search, String qualifiedName, EClass metaclass) { NamedElement result = null; diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/AbstractRepairAction.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/AbstractRepairAction.java new file mode 100644 index 00000000000..6f72a1a82ba --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/AbstractRepairAction.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.uml.modelrepair.internal.stereotypes; + +import org.eclipse.uml2.uml.util.UMLUtil; + + + +/** + * This is the AbstractRepairAction type. Enjoy. + */ +abstract class AbstractRepairAction extends UMLUtil implements IRepairAction { + + private final Kind kind; + + protected AbstractRepairAction(Kind kind) { + this.kind = kind; + } + + public Kind kind() { + return kind; + } + + public boolean isNull() { + return kind() == null; + } + +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/ApplyProfileAction.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/ApplyProfileAction.java new file mode 100644 index 00000000000..c7d456a8400 --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/ApplyProfileAction.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.uml.modelrepair.internal.stereotypes; + +import java.util.Collection; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.emf.common.util.DiagnosticChain; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.papyrus.infra.core.services.ServiceException; +import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForEObject; +import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService; +import org.eclipse.papyrus.infra.services.labelprovider.service.impl.LabelProviderServiceImpl; +import org.eclipse.papyrus.uml.modelrepair.Activator; +import org.eclipse.papyrus.uml.modelrepair.internal.participants.StereotypeApplicationRepairParticipant; +import org.eclipse.papyrus.uml.modelrepair.ui.BrowseProfilesDialog; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.uml2.common.util.UML2Util; +import org.eclipse.uml2.uml.Element; +import org.eclipse.uml2.uml.Package; +import org.eclipse.uml2.uml.Profile; +import org.eclipse.uml2.uml.UMLPackage; + +import com.google.common.base.Supplier; + + +/** + * This is the ApplyProfileAction type. Enjoy. + */ +public class ApplyProfileAction extends AbstractRepairAction { + + private final Element root; + + private Supplier<Profile> profileSupplier; + + public ApplyProfileAction(Element root, Supplier<Profile> profileSupplier) { + super(Kind.APPLY_LATEST_PROFILE_DEFINITION); + + this.root = root; + this.profileSupplier = profileSupplier; + } + + public boolean repair(Resource resource, EPackage profileDefinition, Collection<? extends EObject> stereotypeApplications, DiagnosticChain diagnostics, IProgressMonitor monitor) { + Profile profile = profileSupplier.get(); + if(profile == null) { + return false; + } + + // Get the topmost package + Package topPackage = root.getNearestPackage(); + for(Element higher = topPackage; higher != null; higher = higher.getOwner()) { + Package next = higher.getNearestPackage(); + if(next != null) { + topPackage = next; + } + higher = next; + } + + // Apply the profile + if(topPackage != null) { + StereotypeApplicationRepairParticipant.createStereotypeApplicationMigrator(profile, diagnostics).migrate(stereotypeApplications); + topPackage.applyProfile(profile); + } + + return true; + } + + protected Profile promptForProfile(Shell parentShell) { + Profile result = null; + + LabelProviderService labelProvider = null; + boolean localProvider = false; + try { + labelProvider = ServiceUtilsForEObject.getInstance().getService(LabelProviderService.class, root); + } catch (ServiceException e) { + labelProvider = new LabelProviderServiceImpl(); + localProvider = true; + } + + final BrowseProfilesDialog dlg = new BrowseProfilesDialog(parentShell, labelProvider); + + parentShell.getDisplay().syncExec(new Runnable() { + + public void run() { + dlg.setBlockOnOpen(true); + dlg.open(); + } + }); + + if(localProvider) { + try { + labelProvider.disposeService(); + } catch (ServiceException e) { + Activator.log.error(e); + } + } + + if(dlg.getSelectedProfileURI() != null) { + result = UML2Util.load(root.eResource().getResourceSet(), dlg.getSelectedProfileURI(), UMLPackage.Literals.PROFILE); + } + + return result; + } +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/CreateMarkersAction.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/CreateMarkersAction.java new file mode 100644 index 00000000000..6739708d997 --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/CreateMarkersAction.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.uml.modelrepair.internal.stereotypes; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +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.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.papyrus.infra.services.markerlistener.providers.IMarkerProvider; +import org.eclipse.papyrus.infra.services.markerlistener.util.MarkerListenerUtils; +import org.eclipse.papyrus.uml.modelrepair.Activator; + + +/** + * This is the CreateMarkersAction type. Enjoy. + */ +public class CreateMarkersAction extends AbstractRepairAction { + + static final CreateMarkersAction INSTANCE = new CreateMarkersAction(); + + private CreateMarkersAction() { + super(Kind.CREATE_MARKERS); + } + + public boolean repair(Resource resource, EPackage profileDefinition, Collection<? extends EObject> stereotypeApplications, DiagnosticChain problems, IProgressMonitor monitor) { + List<IMarkerProvider> providers = MarkerListenerUtils.getMarkerProviders(resource); + if(!providers.isEmpty()) { + IMarkerProvider provider = providers.get(0); + BasicDiagnostic diagnostics = new BasicDiagnostic(); + + for(EObject next : stereotypeApplications) { + EObject subject = getBaseElement(next); + if(subject == null) { + // OK, apply it to the application, instead + subject = next; + } + + diagnostics.add(new BasicDiagnostic(Diagnostic.WARNING, Activator.PLUGIN_ID, 0, "Obsolete application of stereotype " + UML2EcoreConverter.getOriginalName(next.eClass()), new Object[]{ subject, next })); + } + + try { + provider.createMarkers(resource, diagnostics, monitor); + } catch (CoreException e) { + if(problems == null) { + Activator.log.error(e); + } else { + problems.add(BasicDiagnostic.toDiagnostic(e.getStatus())); + } + } + } + + return true; + } + +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/DeleteAction.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/DeleteAction.java new file mode 100644 index 00000000000..e39eb511902 --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/DeleteAction.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.uml.modelrepair.internal.stereotypes; + +import java.util.Collection; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.emf.common.util.DiagnosticChain; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; + + +/** + * This is the DeleteAction type. Enjoy. + */ +public class DeleteAction extends AbstractRepairAction { + + static final DeleteAction INSTANCE = new DeleteAction(); + + private DeleteAction() { + super(Kind.DELETE); + } + + public boolean repair(Resource resource, EPackage profileDefinition, Collection<? extends EObject> stereotypeApplications, DiagnosticChain diagnostics, IProgressMonitor monitor) { + monitor = SubMonitor.convert(monitor, stereotypeApplications.size()); + + for(EObject next : stereotypeApplications) { + destroy(next); + monitor.worked(1); + } + + return true; + } + +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/IRepairAction.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/IRepairAction.java new file mode 100644 index 00000000000..ea6f2b61fbc --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/IRepairAction.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.uml.modelrepair.internal.stereotypes; + +import java.util.Collection; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.emf.common.util.DiagnosticChain; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; + +import com.google.common.base.Predicate; + + +/** + * This is the IRepairAction type. Enjoy. + */ +public interface IRepairAction { + + IRepairAction NO_OP = new IRepairAction() { + + public Kind kind() { + return Kind.NO_OP; + } + + public boolean isNull() { + return false; + } + + public boolean repair(Resource resource, EPackage profileDefinition, java.util.Collection<? extends EObject> stereotypeApplications, DiagnosticChain diagnostics, IProgressMonitor monitor) { + return true; + } + }; + + IRepairAction NULL = new IRepairAction() { + + public Kind kind() { + return null; + } + + public boolean isNull() { + return true; + } + + public boolean repair(Resource resource, EPackage profileDefinition, java.util.Collection<? extends EObject> stereotypeApplications, DiagnosticChain diagnostics, IProgressMonitor monitor) { + throw new UnsupportedOperationException("null repair action"); //$NON-NLS-1$ + } + }; + + Predicate<IRepairAction> NOT_NULL = new Predicate<IRepairAction>() { + + public boolean apply(IRepairAction input) { + return (input != null) && !input.isNull(); + } + }; + + Kind kind(); + + boolean isNull(); + + boolean repair(Resource resource, EPackage profileDefinition, Collection<? extends EObject> stereotypeApplications, DiagnosticChain diagnostics, IProgressMonitor monitor); + + // + // Nested types + // + + enum Kind { + /** The lazy option. */ + NO_OP("Postpone"), + /** The option to apply the profile to the model and migrate stereotypes to its latest definition. */ + APPLY_LATEST_PROFILE_DEFINITION("Migrate Profile"), + /** The option to create problem markers for later review. */ + CREATE_MARKERS("Create Markers"), + /** The option to delete all zombies. */ + DELETE("Delete Stereotypes"); + + private final String displayName; + + private Kind(String displayName) { + this.displayName = displayName; + } + + public String displayName() { + return displayName; + } + } +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/StereotypeApplicationRepairSnippet.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/StereotypeApplicationRepairSnippet.java new file mode 100644 index 00000000000..43f580e5840 --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/StereotypeApplicationRepairSnippet.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.uml.modelrepair.internal.stereotypes; + +import java.util.Collection; +import java.util.Set; + +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.notify.impl.AdapterImpl; +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.papyrus.infra.core.editor.IMultiDiagramEditor; +import org.eclipse.papyrus.infra.core.resource.IModelSetSnippet; +import org.eclipse.papyrus.infra.core.resource.ModelSet; +import org.eclipse.papyrus.infra.core.services.ServiceException; +import org.eclipse.papyrus.infra.emf.utils.EMFHelper; +import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForResourceSet; +import org.eclipse.papyrus.uml.modelrepair.ui.ZombieStereotypeDialogPresenter; +import org.eclipse.ui.IEditorPart; +import org.eclipse.uml2.uml.Element; +import org.eclipse.uml2.uml.Package; +import org.eclipse.uml2.uml.Profile; +import org.eclipse.uml2.uml.ProfileApplication; +import org.eclipse.uml2.uml.UMLPackage; + +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; + + +/** + * A snippet on the UML Model for detecting and initiating repair of zombie stereotype applications. + */ +public class StereotypeApplicationRepairSnippet implements IModelSetSnippet { + + private final UMLResourceLoadAdaper adapter = new UMLResourceLoadAdaper(); + + private final Supplier<Profile> dynamicProfileSupplier; + + private ZombieStereotypeDialogPresenter presenter; + + public StereotypeApplicationRepairSnippet() { + this(null); + } + + protected StereotypeApplicationRepairSnippet(Supplier<Profile> dynamicProfileSupplier) { + super(); + + this.dynamicProfileSupplier = dynamicProfileSupplier; + } + + protected void handleResourceLoaded(Resource resource) { + Element root = getRootUMLElement(resource); + + // Only check for zombies in resources that we can modify (those being the resources in the user model opened in the editor) + if((root != null) && !EMFHelper.isReadOnly(resource, EMFHelper.resolveEditingDomain(root))) { + ZombieStereotypesDescriptor zombies = getZombieStereotypes(resource, root); + if((zombies != null) && (presenter != null)) { + presenter.addZombies(zombies); + } + } + } + + protected Element getRootUMLElement(Resource resource) { + return (Element)EcoreUtil.getObjectByType(resource.getContents(), UMLPackage.Literals.ELEMENT); + } + + protected ZombieStereotypesDescriptor getZombieStereotypes(Resource resource, Element root) { + ZombieStereotypesDescriptor result = null; + + Package contextPackage = root.getNearestPackage(); + + // Could be a Class or something that is a disconnected controlled unit? + if(contextPackage != null) { + Collection<ProfileApplication> profileApplications = contextPackage.getAllProfileApplications(); + Set<EPackage> appliedDefinitions = getAppliedDefinitions(profileApplications); + Supplier<Profile> profileSupplier = (dynamicProfileSupplier != null) ? dynamicProfileSupplier : presenter.getDynamicProfileSupplier(); + ZombieStereotypesDescriptor zombies = new ZombieStereotypesDescriptor(resource, root, appliedDefinitions, profileSupplier); + + for(EObject next : resource.getContents()) { + if(!(next instanceof Element)) { + zombies.analyze(next); + } + } + + if(zombies.hasZombies()) { + result = zombies; + } + } + + return result; + } + + protected Set<EPackage> getAppliedDefinitions(Iterable<? extends ProfileApplication> profileApplications) { + Set<EPackage> result = Sets.newHashSet(); + + for(ProfileApplication next : profileApplications) { + EPackage definition = next.getAppliedDefinition(); + if((definition != null) && !definition.eIsProxy()) { + result.add(definition); + } + } + + return result; + } + + // + // Snippet lifecycle + // + + public void start(ModelSet modelsManager) { + try { + IEditorPart editor = ServiceUtilsForResourceSet.getInstance().getService(IMultiDiagramEditor.class, modelsManager); + + if(editor != null) { + // this model is opened in an editor. That is the context in which we want to provide our services + presenter = new ZombieStereotypeDialogPresenter(editor.getSite().getShell(), modelsManager); + adapter.adapt(modelsManager); + } + } catch (ServiceException e) { + // OK, there is no editor, so we aren't needed + } + } + + public void dispose(ModelSet modelsManager) { + if(presenter != null) { + presenter.dispose(); + presenter = null; + } + + adapter.unadapt(modelsManager); + } + + // + // Nested types + // + + private class UMLResourceLoadAdaper extends AdapterImpl { + + @Override + public void notifyChanged(Notification msg) { + Object notifier = msg.getNotifier(); + + if(notifier instanceof ResourceSet) { + handleNotification((ResourceSet)notifier, msg); + } else if(notifier instanceof Resource) { + handleNotification((Resource)notifier, msg); + } + } + + ResourceSet getResourceSet() { + return (ResourceSet)getTarget(); + } + + @Override + public void setTarget(Notifier newTarget) { + if((newTarget == null) || (newTarget instanceof ResourceSet)) { + super.setTarget(newTarget); + } + + if(newTarget instanceof ResourceSet) { + // Iterate a defensive copy because other adapters cause concurrent additions by loading additional resources + for(Resource next : ImmutableList.copyOf(((ResourceSet)newTarget).getResources())) { + adapt(next); + } + } else if(newTarget instanceof Resource) { + Resource resource = (Resource)newTarget; + if(resource.isLoaded()) { + // already loaded? Handled it + handleResourceLoaded(resource); + } + } + } + + @Override + public void unsetTarget(Notifier oldTarget) { + if(oldTarget == getResourceSet()) { + for(Resource next : getResourceSet().getResources()) { + unadapt(next); + } + } + + super.unsetTarget(oldTarget); + } + + protected void adapt(Notifier notifier) { + if(!notifier.eAdapters().contains(this)) { + notifier.eAdapters().add(this); + } + } + + protected void unadapt(Notifier notifier) { + notifier.eAdapters().remove(this); + } + + protected void handleNotification(ResourceSet rset, Notification msg) { + switch(msg.getFeatureID(ResourceSet.class)) { + case ResourceSet.RESOURCE_SET__RESOURCES: + switch(msg.getEventType()) { + case Notification.ADD: + adapt((Resource)msg.getNewValue()); + break; + case Notification.ADD_MANY: + for(Object next : (Collection<?>)msg.getNewValue()) { + adapt((Resource)next); + } + break; + } + break; + } + } + + protected void handleNotification(Resource resource, Notification msg) { + switch(msg.getFeatureID(Resource.class)) { + case Resource.RESOURCE__IS_LOADED: + if(msg.getNewBooleanValue()) { + handleResourceLoaded(resource); + } + break; + } + } + } +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/ZombieStereotypesDescriptor.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/ZombieStereotypesDescriptor.java new file mode 100644 index 00000000000..9ac171511ee --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/internal/stereotypes/ZombieStereotypesDescriptor.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.uml.modelrepair.internal.stereotypes; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.emf.common.util.DiagnosticChain; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.uml2.uml.Element; +import org.eclipse.uml2.uml.Profile; +import org.eclipse.uml2.uml.util.UMLUtil; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + + +/** + * This is the ZombieStereotypesDescriptor type. Enjoy. + */ +public class ZombieStereotypesDescriptor { + + private final Resource resource; + + private final Element root; + + private final Set<EPackage> appliedProfileDefinitions; + + private final Multimap<EPackage, EObject> zombies = ArrayListMultimap.create(); + + private final Map<EPackage, IRepairAction> suggestedActions = Maps.newHashMap(); + + private final Supplier<Profile> dynamicProfileSupplier; + + private Map<EPackage, Map<IRepairAction.Kind, IRepairAction>> repairActions = Maps.newHashMap(); + + public ZombieStereotypesDescriptor(Resource resource, Element root, Set<EPackage> appliedProfileDefinitions, Supplier<Profile> dynamicProfileSupplier) { + this.resource = resource; + this.root = root; + this.appliedProfileDefinitions = appliedProfileDefinitions; + this.dynamicProfileSupplier = dynamicProfileSupplier; + } + + public void analyze(EObject stereotypeApplication) { + EPackage schema = getEPackage(stereotypeApplication); + if((schema == null) || !appliedProfileDefinitions.contains(schema)) { + // It's a zombie + zombies.put(schema, stereotypeApplication); + + if((schema != null) && !suggestedActions.containsKey(schema)) { + suggestedActions.put(schema, computeSuggestedAction(schema)); + } + } + } + + public boolean hasZombies() { + return !zombies.isEmpty(); + } + + public Resource getResource() { + return resource; + } + + public Collection<? extends EPackage> getZombiePackages() { + return zombies.keySet(); + } + + public int getZombieCount(EPackage schema) { + return zombies.get(schema).size(); + } + + public boolean repair(EPackage schema, IRepairAction repairAction, DiagnosticChain diagnostics, IProgressMonitor monitor) { + return repairAction.repair(resource, schema, zombies.get(schema), diagnostics, monitor); + } + + protected EPackage getEPackage(EObject object) { + EClass eclass = object.eClass(); + return (eclass == null) ? null : eclass.getEPackage(); + } + + protected IRepairAction computeSuggestedAction(EPackage schema) { + // Try options in our preferred order + IRepairAction result = getRepairAction(schema, IRepairAction.Kind.APPLY_LATEST_PROFILE_DEFINITION); + if(result.isNull()) { + // This one is always available + result = getRepairAction(schema, IRepairAction.Kind.NO_OP); + } + + return result; + } + + protected Map<IRepairAction.Kind, IRepairAction> computeFeasibleRepairActions(EPackage schema) { + Map<IRepairAction.Kind, IRepairAction> result = Maps.newEnumMap(IRepairAction.Kind.class); + + // Always available + result.put(IRepairAction.NO_OP.kind(), IRepairAction.NO_OP); + result.put(DeleteAction.INSTANCE.kind(), DeleteAction.INSTANCE); + result.put(CreateMarkersAction.INSTANCE.kind(), CreateMarkersAction.INSTANCE); + + Profile profile = findProfile(schema); + Supplier<Profile> supplier = (profile == null) ? dynamicProfileSupplier : Suppliers.ofInstance(profile); + IRepairAction applyProfile = new ApplyProfileAction(root, supplier); + result.put(applyProfile.kind(), applyProfile); + + return result; + } + + public IRepairAction getSuggestedRepairAction(EPackage schema) { + return suggestedActions.get(schema); + } + + public IRepairAction getRepairAction(EPackage schema, IRepairAction.Kind kind) { + Map<IRepairAction.Kind, IRepairAction> available = repairActions.get(schema); + if(available == null) { + available = computeFeasibleRepairActions(schema); + repairActions.put(schema, available); + } + + return available.get(kind); + } + + public List<IRepairAction> getAvailableRepairActions(EPackage schema) { + Map<IRepairAction.Kind, IRepairAction> actions = repairActions.get(schema); + return (actions == null) ? Collections.<IRepairAction> emptyList() : ImmutableList.copyOf(Iterables.filter(actions.values(), IRepairAction.NOT_NULL)); + } + + protected Profile findProfile(EPackage definition) { + return UMLUtil.getProfile(definition, root); + } +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/BrowseProfilesBlock.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/BrowseProfilesBlock.java new file mode 100644 index 00000000000..eea58554438 --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/BrowseProfilesBlock.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2014 CEA 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: + * CEA - Initial API and implementation + * Christian W. Damus (CEA) - bug 431953 (extracted from SwitchProfileDialog) + * + */ +package org.eclipse.papyrus.uml.modelrepair.ui; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.window.Window; +import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService; +import org.eclipse.papyrus.infra.widgets.editors.TreeSelectorDialog; +import org.eclipse.papyrus.infra.widgets.providers.EncapsulatedContentProvider; +import org.eclipse.papyrus.infra.widgets.providers.StaticContentProvider; +import org.eclipse.papyrus.infra.widgets.providers.WorkspaceContentProvider; +import org.eclipse.papyrus.uml.extensionpoints.profile.RegisteredProfile; +import org.eclipse.papyrus.uml.modelrepair.Activator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; + +import com.google.common.eventbus.EventBus; + + +/** + * This is the BrowseProfilesBlock type. Enjoy. + */ +public class BrowseProfilesBlock { + + /** Block style for icon browse buttons. */ + public static final int ICON = 0x00; + + /** Block style for text browser buttons. */ + public static final int TEXT = 0x01; + + private final EventBus bus; + + private final LabelProviderService labelProviderService; + + private Composite control; + + private Button browseWorkspace; + + private Button browseRegistered; + + private boolean enabled = true; + + public BrowseProfilesBlock(EventBus bus, LabelProviderService labelProviderService) { + super(); + + this.bus = bus; + this.labelProviderService = labelProviderService; + } + + public Control createControl(Composite parent, int style) { + control = new Composite(parent, SWT.NONE); + + GridLayout buttonsLayout = new GridLayout(2, false); + buttonsLayout.marginWidth = 0; + + control.setLayout(buttonsLayout); + + final boolean useText = (style & TEXT) == TEXT; + + browseWorkspace = new Button(control, SWT.PUSH); + if(useText) { + browseWorkspace.setText("Workspace..."); + } else { + browseWorkspace.setImage(org.eclipse.papyrus.infra.widgets.Activator.getDefault().getImage("icons/Add_12x12.gif")); + } + + browseRegistered = new Button(control, SWT.PUSH); + if(useText) { + browseRegistered.setText("Registered..."); + } else { + browseRegistered.setImage(org.eclipse.papyrus.infra.widgets.Activator.getDefault().getImage(Activator.PLUGIN_ID, "icons/AddReg.gif")); + } + + SelectionListener buttonListener = new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + if(e.widget == browseWorkspace) { + browseWorkspaceProfiles(); + } else { + browseRegisteredProfiles(); + } + } + }; + browseWorkspace.addSelectionListener(buttonListener); + browseRegistered.addSelectionListener(buttonListener); + + updateEnablement(); + + return control; + } + + public void setEnabled(boolean enabled) { + if(enabled != this.enabled) { + this.enabled = enabled; + updateEnablement(); + } + } + + public boolean isEnabled() { + return enabled; + } + + protected Shell getShell() { + return (control == null) ? null : control.getShell(); + } + + protected void updateEnablement() { + if((browseWorkspace != null) && !browseWorkspace.isDisposed()) { + browseWorkspace.setEnabled(enabled); + browseRegistered.setEnabled(enabled); + } + } + + protected void browseWorkspaceProfiles() { + Map<String, String> extensionFilters = new LinkedHashMap<String, String>(); + extensionFilters.put("*.profile.uml", "UML Profiles (*.profile.uml)"); + extensionFilters.put("*.uml", "UML (*.uml)"); + extensionFilters.put("*", "All (*)"); + + TreeSelectorDialog dialog = new TreeSelectorDialog(getShell()); + dialog.setTitle("Browse Workspace"); + dialog.setDescription("Select a profile in the workspace."); + WorkspaceContentProvider workspaceContentProvider = new WorkspaceContentProvider(); + workspaceContentProvider.setExtensionFilters(extensionFilters); + dialog.setContentProvider(workspaceContentProvider); + + dialog.setLabelProvider(labelProviderService.getLabelProvider()); + + + if(dialog.open() == Window.OK) { + Object[] result = dialog.getResult(); + if(result == null || result.length == 0) { + return; + } + + Object selectedFile = result[0]; + + if(selectedFile instanceof IFile) { + bus.post((IFile)selectedFile); + } + } + } + + protected void browseRegisteredProfiles() { + TreeSelectorDialog dialog = new TreeSelectorDialog(getShell()); + dialog.setTitle("Browse Registered Profiles"); + dialog.setDescription("Select one of the registered profiles below."); + dialog.setContentProvider(new EncapsulatedContentProvider(new StaticContentProvider(RegisteredProfile.getRegisteredProfiles()))); + dialog.setLabelProvider(new LabelProvider() { + + @Override + public Image getImage(Object element) { + if(element instanceof RegisteredProfile) { + RegisteredProfile profile = (RegisteredProfile)element; + return profile.getImage(); + } + return super.getImage(element); + } + + @Override + public String getText(Object element) { + if(element instanceof RegisteredProfile) { + RegisteredProfile profile = (RegisteredProfile)element; + return profile.name; + } + + return super.getText(element); + } + }); + + if(dialog.open() == Window.OK) { + Object[] result = dialog.getResult(); + if(result == null || result.length == 0) { + return; + } + + Object selectedElement = result[0]; + if(selectedElement instanceof RegisteredProfile) { + bus.post((RegisteredProfile)selectedElement); + } + } + } +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/BrowseProfilesDialog.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/BrowseProfilesDialog.java new file mode 100644 index 00000000000..16d24a19084 --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/BrowseProfilesDialog.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.uml.modelrepair.ui; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IPath; +import org.eclipse.emf.common.util.URI; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.jface.window.IShellProvider; +import org.eclipse.jface.window.SameShellProvider; +import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService; +import org.eclipse.papyrus.uml.extensionpoints.profile.RegisteredProfile; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + + +/** + * This is the BrowseProfilesDialog type. Enjoy. + */ +public class BrowseProfilesDialog extends TrayDialog { + + private final LabelProviderService labelProviderService; + + private Text profileText; + + private URI selectedProfileURI; + + public BrowseProfilesDialog(Shell shell, LabelProviderService labelProviderService) { + this(new SameShellProvider(shell), labelProviderService); + } + + public BrowseProfilesDialog(IShellProvider parentShell, LabelProviderService labelProviderService) { + super(parentShell); + + this.labelProviderService = labelProviderService; + } + + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + + newShell.setText("Apply Profile"); + newShell.setMinimumSize(300, 150); + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite contents = (Composite)super.createDialogArea(parent); + + Composite main = new Composite(contents, SWT.NONE); + main.setLayout(new GridLayout(2, false)); + main.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Label messageLabel = new Label(main, SWT.WRAP); + messageLabel.setText("Select a registered or a workspace profile to apply for recovery of unrecognized stereotypes."); + GridData span2 = new GridData(SWT.FILL, SWT.BEGINNING, true, false); + span2.horizontalSpan = 2; + messageLabel.setLayoutData(span2); + + EventBus bus = new EventBus("profileSelection"); //$NON-NLS-1$ + BrowseProfilesBlock block = new BrowseProfilesBlock(bus, labelProviderService); + block.createControl(main, BrowseProfilesBlock.TEXT).setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false, 2, 1)); + + Label profileLabel = new Label(main, SWT.NONE); + profileLabel.setText("Profile:"); + profileText = new Text(main, SWT.BORDER | SWT.READ_ONLY); + profileText.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); + + bus.register(new Object() { + + @Subscribe + public void workspaceProfileSelected(IFile file) { + IPath filePath = file.getFullPath(); + setSelectedProfileURI(URI.createPlatformResourceURI(filePath.toPortableString(), true)); + + updateProfile(); + } + + @Subscribe + public void registeredProfileSelected(RegisteredProfile profile) { + setSelectedProfileURI(profile.uri); + } + }); + + return contents; + } + + protected Control createContents(Composite parent) { + setHelpAvailable(false); + Control result = super.createContents(parent); + updateProfile(); + return result; + } + + public void setSelectedProfileURI(URI selectedProfileURI) { + this.selectedProfileURI = selectedProfileURI; + updateProfile(); + } + + public URI getSelectedProfileURI() { + return selectedProfileURI; + } + + @Override + protected void cancelPressed() { + selectedProfileURI = null; + super.cancelPressed(); + } + + protected void updateProfile() { + if((profileText != null) && !profileText.isDisposed()) { + if(selectedProfileURI == null) { + profileText.setText(""); //$NON-NLS-1$ + } else { + profileText.setText(selectedProfileURI.toString()); + } + + getButton(IDialogConstants.OK_ID).setEnabled(selectedProfileURI != null); + } + } +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/SwitchProfileDialog.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/SwitchProfileDialog.java index cc85cdfc6e3..4d714ed856c 100644 --- a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/SwitchProfileDialog.java +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/SwitchProfileDialog.java @@ -18,7 +18,6 @@ import static com.google.common.base.Strings.nullToEmpty; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
@@ -55,7 +54,6 @@ import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
-import org.eclipse.jface.window.Window;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.services.ServiceException;
import org.eclipse.papyrus.infra.core.utils.TransactionHelper;
@@ -64,20 +62,14 @@ import org.eclipse.papyrus.infra.emf.resource.Replacement; import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForResourceSet;
import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService;
import org.eclipse.papyrus.infra.services.markerlistener.dialogs.DiagnosticDialog;
-import org.eclipse.papyrus.infra.widgets.editors.TreeSelectorDialog;
-import org.eclipse.papyrus.infra.widgets.providers.EncapsulatedContentProvider;
-import org.eclipse.papyrus.infra.widgets.providers.StaticContentProvider;
-import org.eclipse.papyrus.infra.widgets.providers.WorkspaceContentProvider;
import org.eclipse.papyrus.uml.extensionpoints.profile.RegisteredProfile;
import org.eclipse.papyrus.uml.modelrepair.Activator;
import org.eclipse.papyrus.uml.tools.util.ProfileHelper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
-import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
@@ -89,6 +81,9 @@ import org.eclipse.ui.dialogs.SelectionDialog; import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.uml2.uml.Profile;
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+
/**
* The dialog to switch from a Profile application to another
*
@@ -100,10 +95,6 @@ public class SwitchProfileDialog extends SelectionDialog { private static final String APPLY_LABEL = "Apply";
- private static final int BROWSE_WORKSPACE_ID = IDialogConstants.CLIENT_ID + 2;
-
- private static final int BROWSE_REGISTERED_ID = IDialogConstants.CLIENT_ID + 3;
-
private ModelSet modelSet;
private TransactionalEditingDomain editingDomain;
@@ -112,6 +103,8 @@ public class SwitchProfileDialog extends SelectionDialog { protected Table table;
+ protected BrowseProfilesBlock browseBlock;
+
protected LabelProviderService labelProviderService;
protected final Map<Resource, Resource> profilesToEdit = new HashMap<Resource, Resource>();
@@ -148,20 +141,25 @@ public class SwitchProfileDialog extends SelectionDialog { warningLabel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
- Composite buttonsBarComposite = new Composite(self, SWT.NONE);
-
- GridLayout buttonsLayout = new GridLayout(0, false);
- buttonsLayout.marginWidth = 0;
+ EventBus bus = new EventBus("profileSelection"); //$NON-NLS-1$
+ browseBlock = new BrowseProfilesBlock(bus, labelProviderService);
+ browseBlock.createControl(self, BrowseProfilesBlock.ICON).setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
+ bus.register(new Object() {
- buttonsBarComposite.setLayout(buttonsLayout);
- buttonsBarComposite.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
-
- Button browseWorkspace = createButton(buttonsBarComposite, BROWSE_WORKSPACE_ID, "", false);
- browseWorkspace.setImage(org.eclipse.papyrus.infra.widgets.Activator.getDefault().getImage("icons/Add_12x12.gif"));
- Button browseRegistered = createButton(buttonsBarComposite, BROWSE_REGISTERED_ID, "", false);
- browseRegistered.setImage(org.eclipse.papyrus.infra.widgets.Activator.getDefault().getImage(Activator.PLUGIN_ID, "icons/AddReg.gif"));
+ @Subscribe
+ public void workspaceProfileSelected(IFile file) {
+ IPath filePath = file.getFullPath();
+ URI workspaceURI = URI.createPlatformResourceURI(filePath.toPortableString(), true);
+ replaceSelectionWith(workspaceURI);
+ }
+ @Subscribe
+ public void registeredProfileSelected(RegisteredProfile profile) {
+ replaceSelectionWith(profile.uri);
+ }
+ });
+
viewer = new TableViewer(self, SWT.FULL_SELECTION | SWT.BORDER);
table = viewer.getTable();
TableLayout layout = new TableLayout();
@@ -266,22 +264,11 @@ public class SwitchProfileDialog extends SelectionDialog { boolean enableBrowse = !viewer.getSelection().isEmpty();
- getButton(BROWSE_REGISTERED_ID).setEnabled(enableBrowse);
- getButton(BROWSE_WORKSPACE_ID).setEnabled(enableBrowse);
+ browseBlock.setEnabled(enableBrowse);
viewer.refresh();
}
- @Override
- protected void setButtonLayoutData(Button button) {
- int buttonId = ((Integer)button.getData()).intValue();
- if(buttonId == BROWSE_REGISTERED_ID || buttonId == BROWSE_WORKSPACE_ID) {
- return; //Don't change the layout data
- }
-
- super.setButtonLayoutData(button);
- }
-
protected void applyPressed() {
if(profilesToEdit.isEmpty()) {
return;
@@ -356,12 +343,6 @@ public class SwitchProfileDialog extends SelectionDialog { case APPLY_ID:
applyPressed();
return;
- case BROWSE_REGISTERED_ID:
- browseRegisteredProfiles();
- return;
- case BROWSE_WORKSPACE_ID:
- browseWorkspaceProfiles();
- return;
}
super.buttonPressed(buttonId);
@@ -458,43 +439,6 @@ public class SwitchProfileDialog extends SelectionDialog { }
}
- protected void browseWorkspaceProfiles() {
- if(getSelectedResource() == null) {
- return;
- }
-
- Map<String, String> extensionFilters = new LinkedHashMap<String, String>();
- extensionFilters.put("*.profile.uml", "UML Profiles (*.profile.uml)");
- extensionFilters.put("*.uml", "UML (*.uml)");
- extensionFilters.put("*", "All (*)");
-
- TreeSelectorDialog dialog = new TreeSelectorDialog(getShell());
- dialog.setTitle("Browse Workspace");
- dialog.setDescription("Select a profile in the workspace.");
- WorkspaceContentProvider workspaceContentProvider = new WorkspaceContentProvider();
- workspaceContentProvider.setExtensionFilters(extensionFilters);
- dialog.setContentProvider(workspaceContentProvider);
-
- dialog.setLabelProvider(labelProviderService.getLabelProvider());
-
-
- if(dialog.open() == Window.OK) {
- Object[] result = dialog.getResult();
- if(result == null || result.length == 0) {
- return;
- }
-
- Object selectedFile = result[0];
-
- if(selectedFile instanceof IFile) {
- IPath filePath = ((IFile)selectedFile).getFullPath();
- URI workspaceURI = URI.createPlatformResourceURI(filePath.toString(), true);
-
- replaceSelectionWith(workspaceURI);
- }
- }
- }
-
protected Resource getSelectedResource() {
ISelection selection = viewer.getSelection();
if(selection.isEmpty()) {
@@ -511,48 +455,6 @@ public class SwitchProfileDialog extends SelectionDialog { return null;
}
- protected void browseRegisteredProfiles() {
- TreeSelectorDialog dialog = new TreeSelectorDialog(getShell());
- dialog.setTitle("Browse Registered Profiles");
- dialog.setDescription("Select one of the registered profiles below.");
- dialog.setContentProvider(new EncapsulatedContentProvider(new StaticContentProvider(RegisteredProfile.getRegisteredProfiles())));
- dialog.setLabelProvider(new LabelProvider() {
-
- @Override
- public Image getImage(Object element) {
- if(element instanceof RegisteredProfile) {
- RegisteredProfile profile = (RegisteredProfile)element;
- return profile.getImage();
- }
- return super.getImage(element);
- }
-
- @Override
- public String getText(Object element) {
- if(element instanceof RegisteredProfile) {
- RegisteredProfile profile = (RegisteredProfile)element;
- return profile.name;
- }
-
- return super.getText(element);
- }
- });
-
- if(dialog.open() == Window.OK) {
- Object[] result = dialog.getResult();
- if(result == null || result.length == 0) {
- return;
- }
-
- Object selectedElement = result[0];
- if(selectedElement instanceof RegisteredProfile) {
- RegisteredProfile profile = (RegisteredProfile)selectedElement;
-
- replaceSelectionWith(profile.uri);
- }
- }
- }
-
protected void replaceSelectionWith(URI targetURI) {
Resource targetResource = modelSet.getResource(targetURI, true);
diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/ZombieStereotypeDialogPresenter.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/ZombieStereotypeDialogPresenter.java new file mode 100644 index 00000000000..2ebb808daaa --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/ZombieStereotypeDialogPresenter.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.uml.modelrepair.ui; + +import java.util.List; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.window.Window; +import org.eclipse.papyrus.infra.core.resource.ModelSet; +import org.eclipse.papyrus.infra.core.services.ServiceException; +import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForResourceSet; +import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService; +import org.eclipse.papyrus.infra.services.labelprovider.service.impl.LabelProviderServiceImpl; +import org.eclipse.papyrus.uml.modelrepair.Activator; +import org.eclipse.papyrus.uml.modelrepair.internal.stereotypes.ZombieStereotypesDescriptor; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.statushandlers.StatusManager; +import org.eclipse.uml2.common.util.UML2Util; +import org.eclipse.uml2.uml.Profile; +import org.eclipse.uml2.uml.UMLPackage; + +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + + +/** + * This is the ZombieStereotypeDialogPresenter type. Enjoy. + */ +public class ZombieStereotypeDialogPresenter { + + private final Shell parentShell; + + private final ModelSet modelSet; + + private final List<ZombieStereotypesDescriptor> zombieDescriptors = Lists.newArrayListWithExpectedSize(1); + + private final BrowseProfileSupplier dynamicProfileSupplier = new BrowseProfileSupplier(); + + private Runnable presentation; + + public ZombieStereotypeDialogPresenter(Shell parentShell, ModelSet modelSet) { + super(); + + this.parentShell = parentShell; + this.modelSet = modelSet; + } + + public void dispose() { + synchronized(zombieDescriptors) { + zombieDescriptors.clear(); + presentation = null; + } + } + + public BrowseProfileSupplier getDynamicProfileSupplier() { + return dynamicProfileSupplier; + } + + public void addZombies(ZombieStereotypesDescriptor zombies) { + synchronized(zombieDescriptors) { + zombieDescriptors.add(zombies); + + if(presentation == null) { + presentation = new Runnable() { + + public void run() { + List<ZombieStereotypesDescriptor> zombies; + + synchronized(zombieDescriptors) { + if(presentation != this) { + // I have been cancelled + return; + } + zombies = ImmutableList.copyOf(zombieDescriptors); + dispose(); + } + + if(!zombies.isEmpty()) { + try { + ZombieStereotypesDialog zombieDialog = new ZombieStereotypesDialog(parentShell, modelSet, zombies); + dynamicProfileSupplier.setParentWindow(zombieDialog); + zombieDialog.setBlockOnOpen(true); + zombieDialog.open(); + } catch (ServiceException e) { + StatusManager.getManager().handle(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to open model repair dialog.", e), StatusManager.SHOW); + } finally { + dynamicProfileSupplier.setParentWindow(null); + } + } + } + }; + + parentShell.getDisplay().asyncExec(presentation); + } + } + } + + // + // Nested types + // + + private class BrowseProfileSupplier implements Supplier<Profile> { + + private Window parentWindow; + + void setParentWindow(Window parentWindow) { + this.parentWindow = parentWindow; + } + + public Profile get() { + Profile result = null; + + LabelProviderService labelProvider = null; + boolean localProvider = false; + try { + labelProvider = ServiceUtilsForResourceSet.getInstance().getService(LabelProviderService.class, modelSet); + } catch (ServiceException e) { + labelProvider = new LabelProviderServiceImpl(); + localProvider = true; + } + + final BrowseProfilesDialog dlg = new BrowseProfilesDialog(parentWindow.getShell(), labelProvider); + + parentShell.getDisplay().syncExec(new Runnable() { + + public void run() { + dlg.setBlockOnOpen(true); + dlg.open(); + } + }); + + if(localProvider) { + try { + labelProvider.disposeService(); + } catch (ServiceException e) { + Activator.log.error(e); + } + } + + if(dlg.getSelectedProfileURI() != null) { + result = UML2Util.load(modelSet, dlg.getSelectedProfileURI(), UMLPackage.Literals.PROFILE); + } + + return result; + } + } +} diff --git a/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/ZombieStereotypesDialog.java b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/ZombieStereotypesDialog.java new file mode 100644 index 00000000000..6e1a095aad8 --- /dev/null +++ b/plugins/uml/org.eclipse.papyrus.uml.modelrepair/src/org/eclipse/papyrus/uml/modelrepair/ui/ZombieStereotypesDialog.java @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2013, 2014 CEA 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: + * CEA - Initial API and implementation + * Christian W. Damus (CEA) - bug 431953 (adapted from SwitchProfileDialog) + * + */ +package org.eclipse.papyrus.uml.modelrepair.ui; + +import java.lang.reflect.InvocationTargetException; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +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.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.transaction.RecordingCommand; +import org.eclipse.emf.transaction.TransactionalEditingDomain; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.ComboBoxViewerCellEditor; +import org.eclipse.jface.viewers.EditingSupport; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableLayout; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.jface.window.IShellProvider; +import org.eclipse.jface.window.SameShellProvider; +import org.eclipse.papyrus.infra.core.resource.ModelSet; +import org.eclipse.papyrus.infra.core.services.ServiceException; +import org.eclipse.papyrus.infra.core.utils.TransactionHelper; +import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForResourceSet; +import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService; +import org.eclipse.papyrus.infra.services.markerlistener.dialogs.DiagnosticDialog; +import org.eclipse.papyrus.uml.modelrepair.Activator; +import org.eclipse.papyrus.uml.modelrepair.internal.stereotypes.IRepairAction; +import org.eclipse.papyrus.uml.modelrepair.internal.stereotypes.ZombieStereotypesDescriptor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.statushandlers.StatusManager; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; + + +/** + * This is the ZombieStereotypesDialog type. Enjoy. + */ +public class ZombieStereotypesDialog extends TrayDialog { + + private static final int APPLY_ID = IDialogConstants.CLIENT_ID + 1; + + private final TransactionalEditingDomain editingDomain; + + private TableViewer table; + + private LabelProviderService labelProviderService; + + private final List<ZombieStereotypesDescriptor> zombieDescriptors; + + private List<MissingSchema> missingSchemas; + + private final Collection<MissingSchema> actionsToApply; + + /** + * @param shell + */ + public ZombieStereotypesDialog(Shell shell, ModelSet modelSet, Iterable<? extends ZombieStereotypesDescriptor> zombies) throws ServiceException { + this(new SameShellProvider(shell), modelSet, zombies); + } + + /** + * @param parentShell + */ + public ZombieStereotypesDialog(IShellProvider parentShell, ModelSet modelSet, Iterable<? extends ZombieStereotypesDescriptor> zombies) throws ServiceException { + super(parentShell); + + this.editingDomain = modelSet.getTransactionalEditingDomain(); + this.zombieDescriptors = Lists.newArrayList(zombies); + this.labelProviderService = ServiceUtilsForResourceSet.getInstance().getService(LabelProviderService.class, modelSet); + this.actionsToApply = createActionsToApply(); + } + + private Collection<MissingSchema> createActionsToApply() { + return new AbstractCollection<ZombieStereotypesDialog.MissingSchema>() { + + private final Predicate<MissingSchema> filter = new Predicate<MissingSchema>() { + + public boolean apply(MissingSchema input) { + return input.getSelectedRepairAction().kind() != IRepairAction.Kind.NO_OP; + } + }; + + @Override + public Iterator<MissingSchema> iterator() { + return Iterators.filter(getMissingSchemas().iterator(), filter); + } + + @Override + public boolean isEmpty() { + return !Iterables.any(getMissingSchemas(), filter); + } + + @Override + public int size() { + return Iterators.size(iterator()); + } + }; + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite contents = (Composite)super.createDialogArea(parent); + + Composite self = new Composite(contents, SWT.NONE); + self.setLayout(new GridLayout(1, false)); + self.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Label descriptionLabel = new Label(self, SWT.WRAP); + String description = "For each missing profile definition, select an action to correct the problem. Recommended actions are selected already where appropriate."; + descriptionLabel.setText(description); + descriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); + + table = new TableViewer(self, SWT.FULL_SELECTION | SWT.BORDER); + Table tableControl = table.getTable(); + TableLayout layout = new TableLayout(); + tableControl.setLayout(layout); + tableControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + tableControl.setHeaderVisible(true); + + TableColumn nameColumn = new TableColumn(tableControl, SWT.NONE); + nameColumn.setText("Resource"); + layout.addColumnData(new ColumnWeightData(25, 250, true)); + + TableColumn affectedColumn = new TableColumn(tableControl, SWT.NONE); + affectedColumn.setText("Affected"); + layout.addColumnData(new ColumnWeightData(5, 50, true)); + + TableColumn schemaColumn = new TableColumn(tableControl, SWT.NONE); + schemaColumn.setText("Profile"); + layout.addColumnData(new ColumnWeightData(15, 150, true)); + + TableViewerColumn actionColumn = new TableViewerColumn(table, SWT.NONE); + actionColumn.getColumn().setText("Action"); + layout.addColumnData(new ColumnWeightData(10, 100, true)); + actionColumn.setEditingSupport(new ActionEditingSupport(table)); + + table.setContentProvider(ArrayContentProvider.getInstance()); + table.setLabelProvider(new ZombiesLabelProvider()); + table.setInput(getMissingSchemas()); + + return contents; + } + + protected List<MissingSchema> getMissingSchemas() { + if(missingSchemas == null) { + missingSchemas = Lists.newArrayList(); + + for(ZombieStereotypesDescriptor next : zombieDescriptors) { + for(EPackage schema : next.getZombiePackages()) { + missingSchemas.add(new MissingSchema(schema, next)); + } + } + } + + return missingSchemas; + } + + protected void updateControls() { + String newTitle = "Repair Stereotypes"; + if(!actionsToApply.isEmpty()) { + newTitle = newTitle + " *"; + } + getShell().setText(newTitle); + getButton(APPLY_ID).setEnabled(!actionsToApply.isEmpty()); + + table.refresh(); + } + + protected void applyPressed() { + if(actionsToApply.isEmpty()) { + return; + } + + final List<MissingSchema> repairActions = Lists.newArrayList(actionsToApply); + editingDomain.getCommandStack().execute(new RecordingCommand(editingDomain, "Repair stereotypes") { + + @Override + protected void doExecute() { + + final BasicDiagnostic diagnostics = new BasicDiagnostic(Activator.PLUGIN_ID, 0, "Problems in repairing stereotypes", null); + + IRunnableWithProgress runnable = TransactionHelper.createPrivilegedRunnableWithProgress(editingDomain, new IRunnableWithProgress() { + + public void run(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor, actionsToApply.size()); + + for(Iterator<MissingSchema> iter = repairActions.iterator(); iter.hasNext();) { + if(!iter.next().apply(diagnostics, subMonitor.newChild(1))) { + // Leave this one to try it again + iter.remove(); + } + } + + subMonitor.done(); + } + }); + + try { + PlatformUI.getWorkbench().getProgressService().busyCursorWhile(runnable); + } catch (Exception e) { + Throwable t = e; + if(e instanceof InvocationTargetException) { + t = ((InvocationTargetException)e).getTargetException(); + } + StatusManager.getManager().handle(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to repair stereotypes.", t), StatusManager.BLOCK | StatusManager.LOG); + } + + if(diagnostics.getSeverity() > Diagnostic.OK) { + DiagnosticDialog dialog = new DiagnosticDialog(getShell(), "Problems in Repairing Stereotypes", "Some repair actions could not be completed normally. Please review the specific details and take any corrective action that may be required.", diagnostics, Diagnostic.ERROR | Diagnostic.WARNING); + dialog.setBlockOnOpen(true); + dialog.open(); + } + } + }); + + getMissingSchemas().removeAll(repairActions); + updateControls(); + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, APPLY_ID, "Apply", true); + super.createButtonsForButtonBar(parent); + } + + @Override + protected void buttonPressed(int buttonId) { + switch(buttonId) { + case IDialogConstants.CANCEL_ID: + if(!actionsToApply.isEmpty() && !MessageDialog.openQuestion(getShell(), "Repair Stereotypes", "You have not yet applied the pending repair actions. Are you sure you want to cancel?")) { + // don't cancel + return; + } + break; + case APPLY_ID: + applyPressed(); + return; + } + + super.buttonPressed(buttonId); + } + + @Override + public void create() { + super.create(); + + getShell().setText("Repair Stereotypes"); + getShell().setMinimumSize(600, 400); + getShell().pack(); + + updateControls(); + } + + @Override + protected boolean isResizable() { + return true; + } + + @Override + public boolean isHelpAvailable() { + return false; + } + + @Override + protected void okPressed() { + applyPressed(); + + super.okPressed(); + } + + @Override + public boolean close() { + zombieDescriptors.clear(); + if(missingSchemas != null) { + missingSchemas.clear(); + } + + return super.close(); + } + + // + // Nested types + // + + private class ZombiesLabelProvider extends ColumnLabelProvider { + + public ZombiesLabelProvider() { + super(); + } + + @Override + public void update(ViewerCell cell) { + switch(cell.getColumnIndex()) { + case 0: + updateResource(cell); + break; + case 1: + updateAffected(cell); + break; + case 2: + updateSchema(cell); + break; + case 3: + updateAction(cell); + break; + } + } + + void updateResource(ViewerCell cell) { + Resource resource = ((MissingSchema)cell.getElement()).getResource(); + cell.setText(labelProviderService.getLabelProvider().getText(resource)); + cell.setImage(labelProviderService.getLabelProvider().getImage(resource)); + } + + void updateAffected(ViewerCell cell) { + int count = ((MissingSchema)cell.getElement()).getAffectedCount(); + cell.setText(Integer.toString(count)); + } + + void updateSchema(ViewerCell cell) { + EPackage schema = ((MissingSchema)cell.getElement()).getSchema(); + + // If it's an unrecognized schema, then we're not going to have an EPackage name + cell.setText((schema.getName() == null) ? String.format("(%s)", schema.getNsPrefix()) : labelProviderService.getLabelProvider().getText(schema)); + cell.setImage(labelProviderService.getLabelProvider().getImage(schema)); + } + + void updateAction(ViewerCell cell) { + IRepairAction action = ((MissingSchema)cell.getElement()).getSelectedRepairAction(); + cell.setText(action.kind().displayName()); + } + } + + private class MissingSchema { + + private final EPackage ePackage; + + private final ZombieStereotypesDescriptor descriptor; + + private IRepairAction selectedAction; + + MissingSchema(EPackage ePackage, ZombieStereotypesDescriptor descriptor) { + this.ePackage = ePackage; + this.descriptor = descriptor; + this.selectedAction = descriptor.getSuggestedRepairAction(ePackage); + } + + Resource getResource() { + return descriptor.getResource(); + } + + int getAffectedCount() { + return descriptor.getZombieCount(getSchema()); + } + + EPackage getSchema() { + return ePackage; + } + + List<IRepairAction> getRepairActions() { + return descriptor.getAvailableRepairActions(ePackage); + } + + IRepairAction getSelectedRepairAction() { + return selectedAction; + } + + void setSelectedRepairAction(IRepairAction action) { + this.selectedAction = action; + } + + boolean apply(DiagnosticChain diagnostics, IProgressMonitor monitor) { + return descriptor.repair(getSchema(), getSelectedRepairAction(), diagnostics, monitor); + } + } + + private class ActionEditingSupport extends EditingSupport { + + private ComboBoxViewerCellEditor editor; + + ActionEditingSupport(ColumnViewer viewer) { + super(viewer); + } + + @Override + protected CellEditor getCellEditor(Object element) { + if(editor == null) { + editor = new ComboBoxViewerCellEditor((Table)getViewer().getControl(), SWT.BORDER); + editor.setContentProvider(ArrayContentProvider.getInstance()); + editor.setLabelProvider(new LabelProvider() { + + @Override + public String getText(Object element) { + return ((IRepairAction)element).kind().displayName(); + } + }); + } + + editor.setInput(((MissingSchema)element).getRepairActions()); + return editor; + } + + @Override + protected boolean canEdit(Object element) { + return true; + } + + @Override + protected Object getValue(Object element) { + return ((MissingSchema)element).getSelectedRepairAction(); + } + + @Override + protected void setValue(Object element, Object value) { + MissingSchema missing = (MissingSchema)element; + IRepairAction action = (IRepairAction)value; + + if(missing.getSelectedRepairAction() != action) { + missing.setSelectedRepairAction(action); + + editor.getControl().getDisplay().asyncExec(new Runnable() { + + public void run() { + updateControls(); + } + }); + } + } + } +} |