Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2016-02-11 02:48:20 +0000
committerGerrit Code Review @ Eclipse.org2016-02-12 15:31:41 +0000
commitecd4928b327f5561364c5068c9ff5f1668e92d13 (patch)
tree7c34f46cf82a1d65ac753fa92c2a5d55371b8dba /plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src
parent751a204d74e15eb2db6b41c937691fc56dcc1252 (diff)
downloadorg.eclipse.papyrus-ecd4928b327f5561364c5068c9ff5f1668e92d13.tar.gz
org.eclipse.papyrus-ecd4928b327f5561364c5068c9ff5f1668e92d13.tar.xz
org.eclipse.papyrus-ecd4928b327f5561364c5068c9ff5f1668e92d13.zip
Bug 485220: [Architecture] Provide a more modular architecture
https://bugs.eclipse.org/bugs/show_bug.cgi?id=485220 Factor UI dependencies out of the UML Element Types bundle. This includes moving some advices that interact with the user into a new org.eclipse.papyrus.uml.service.types.ui bundle. Pull up the PasteCommandService and IPasteCommandProvider API into the Infra Diagram layer where the extension point is defined. Deprecate the old API in the UML layer. Introduce a service for participation of languages in CSS styling: * styling reset actions in the Reset Style command * access to semantic model classes and properties to make available to CSS Factor PapyrusObservableValue and cohorts out of the UML Tools bundle into the Infra Layer for more general reuse and to relieve the Diagram Infrastructure layer of UML dependencies. The old API remains as deprecated. Remove the Infra Diagram Layer dependency on UML Layer for property testers governing deletion in the diagram. Includes introduction of a new IGraphicalDeletionHelper OSGi service for delegation of the determination of whether an element can be deleted from the diagram and replacement of the XML expression properties * org.eclipse.papyrus.uml.diagram.common.isSemanticDeletion * org.eclipse.papyrus.uml.diagram.common.isReadOnly by * org.eclipse.papyrus.infra.gmfdiag.common.isSemanticDeletion * org.eclipse.papyrus.infra.gmfdiag.common.canDelete (where the latter is the negation of the property that it supersedes) Extract UML dependencies from the Diagram Outline and CSS Editor bundles. Remove unused MDTUtil APIs that referenced a UML-specific annotation. Move the Diagram Infrastructure CSS Palette bundle into the UML layer because it serves to provide extensions on the Palette Service, which is an overtly UML-specific capability. All client APIs for the Properties View are moved from org.eclipse.papyrus.views.properties bundle to a new org.eclipse.papyrus.infra.properties.ui bundle. This includes renaming of: * extension points * label-provider contexts * XWT namespaces Add an "all UI tests" suite. Define a componentized hierarchical build layout of the main plug-ins Change-Id: I43f8f3644857a18b69715f5a2f1da9b1cf286d67
Diffstat (limited to 'plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src')
-rw-r--r--plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/helpers/SafeDialogOpenerDuringValidation.java144
-rw-r--r--plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/validation/ProfileApplicationDuplicationChecker.java670
2 files changed, 396 insertions, 418 deletions
diff --git a/plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/helpers/SafeDialogOpenerDuringValidation.java b/plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/helpers/SafeDialogOpenerDuringValidation.java
index 5d632be0209..91a333f8d74 100644
--- a/plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/helpers/SafeDialogOpenerDuringValidation.java
+++ b/plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/helpers/SafeDialogOpenerDuringValidation.java
@@ -1,72 +1,72 @@
-/*****************************************************************************
- * Copyright (c) 2011 Atos Origin.
- *
- *
- * 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:
- * Atos Origin - Initial API and implementation
- *
- *****************************************************************************/
-package org.eclipse.papyrus.uml.controlmode.profile.helpers;
-
-import org.eclipse.core.commands.operations.IOperationApprover2;
-import org.eclipse.core.commands.operations.IOperationHistory;
-import org.eclipse.core.commands.operations.IUndoableOperation;
-import org.eclipse.core.runtime.IAdaptable;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.papyrus.commands.CheckedOperationHistory;
-
-/**
- * This class must be used to open a dialog during a validation. Its usage avoids side effects with the Properties view, which would throw an {@link IllegalStateException}. The parameterizing class can be used to return a result from the dialog (use
- * {@link Void} if no result is expected).
- */
-public abstract class SafeDialogOpenerDuringValidation<ReturnType> {
-
-
- /**
- * This approver is used to disable any operation during opening of a popup to avoid side
- * effects
- */
- private static IOperationApprover2 operationDisapprover = new IOperationApprover2() {
-
- public IStatus proceedUndoing(IUndoableOperation operation, IOperationHistory history, IAdaptable info) {
- return Status.CANCEL_STATUS;
- }
-
- public IStatus proceedRedoing(IUndoableOperation operation, IOperationHistory history, IAdaptable info) {
- return Status.CANCEL_STATUS;
- }
-
- public IStatus proceedExecuting(IUndoableOperation operation, IOperationHistory history, IAdaptable info) {
- return Status.CANCEL_STATUS;
- }
- };
-
- /**
- * Subclasses must implement this method with the dialog opening. If no result is expected, return null.
- */
- protected abstract ReturnType openDialog();
-
- /**
- * Run the dialog opening with necessary precautions.
- */
- public final ReturnType execute() {
- /*
- * We are currently validating an ongoing operation. Opening a popup here may have
- * side-effects such as re-launching the same operation. (the editor may have not been
- * deactivated yet, and its loss of focus will open a new operation) For this reason, we
- * temporarily disable all operations on the history, just enough time for opening the
- * popup.
- */
- IOperationHistory history = CheckedOperationHistory.getInstance();
- history.addOperationApprover(operationDisapprover);
- ReturnType result = openDialog();
- history.removeOperationApprover(operationDisapprover);
- return result;
- }
-}
+/*****************************************************************************
+ * Copyright (c) 2011 Atos Origin.
+ *
+ *
+ * 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:
+ * Atos Origin - Initial API and implementation
+ *
+ *****************************************************************************/
+package org.eclipse.papyrus.uml.controlmode.profile.helpers;
+
+import org.eclipse.core.commands.operations.IOperationApprover2;
+import org.eclipse.core.commands.operations.IOperationHistory;
+import org.eclipse.core.commands.operations.IUndoableOperation;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.papyrus.infra.emf.gmf.command.CheckedOperationHistory;
+
+/**
+ * This class must be used to open a dialog during a validation. Its usage avoids side effects with the Properties view, which would throw an {@link IllegalStateException}. The parameterizing class can be used to return a result from the dialog (use
+ * {@link Void} if no result is expected).
+ */
+public abstract class SafeDialogOpenerDuringValidation<ReturnType> {
+
+
+ /**
+ * This approver is used to disable any operation during opening of a popup to avoid side
+ * effects
+ */
+ private static IOperationApprover2 operationDisapprover = new IOperationApprover2() {
+
+ public IStatus proceedUndoing(IUndoableOperation operation, IOperationHistory history, IAdaptable info) {
+ return Status.CANCEL_STATUS;
+ }
+
+ public IStatus proceedRedoing(IUndoableOperation operation, IOperationHistory history, IAdaptable info) {
+ return Status.CANCEL_STATUS;
+ }
+
+ public IStatus proceedExecuting(IUndoableOperation operation, IOperationHistory history, IAdaptable info) {
+ return Status.CANCEL_STATUS;
+ }
+ };
+
+ /**
+ * Subclasses must implement this method with the dialog opening. If no result is expected, return null.
+ */
+ protected abstract ReturnType openDialog();
+
+ /**
+ * Run the dialog opening with necessary precautions.
+ */
+ public final ReturnType execute() {
+ /*
+ * We are currently validating an ongoing operation. Opening a popup here may have
+ * side-effects such as re-launching the same operation. (the editor may have not been
+ * deactivated yet, and its loss of focus will open a new operation) For this reason, we
+ * temporarily disable all operations on the history, just enough time for opening the
+ * popup.
+ */
+ IOperationHistory history = CheckedOperationHistory.getInstance();
+ history.addOperationApprover(operationDisapprover);
+ ReturnType result = openDialog();
+ history.removeOperationApprover(operationDisapprover);
+ return result;
+ }
+}
diff --git a/plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/validation/ProfileApplicationDuplicationChecker.java b/plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/validation/ProfileApplicationDuplicationChecker.java
index ce965ff8dcd..a0fc25bb232 100644
--- a/plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/validation/ProfileApplicationDuplicationChecker.java
+++ b/plugins/uml/org.eclipse.papyrus.uml.controlmode.profile/src/org/eclipse/papyrus/uml/controlmode/profile/validation/ProfileApplicationDuplicationChecker.java
@@ -1,346 +1,324 @@
-/*****************************************************************************
- * Copyright (c) 2011 Atos Origin.
- *
- *
- * 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:
- * Atos Origin - Initial API and implementation
- *
- *****************************************************************************/
-package org.eclipse.papyrus.uml.controlmode.profile.validation;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.emf.common.notify.Notification;
-import org.eclipse.emf.common.util.TreeIterator;
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.InternalEObject;
-import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
-import org.eclipse.emf.edit.domain.EditingDomain;
-import org.eclipse.emf.validation.AbstractModelConstraint;
-import org.eclipse.emf.validation.EMFEventType;
-import org.eclipse.emf.validation.IValidationContext;
-import org.eclipse.gmf.runtime.emf.core.util.EMFCoreUtil;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.papyrus.infra.core.services.ServiceException;
-import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForEObject;
-import org.eclipse.papyrus.infra.services.resourceloading.preferences.StrategyChooser;
-import org.eclipse.papyrus.infra.widgets.toolbox.notification.builders.NotificationBuilder;
-import org.eclipse.papyrus.uml.controlmode.profile.Activator;
-import org.eclipse.papyrus.uml.controlmode.profile.Messages;
-import org.eclipse.papyrus.uml.controlmode.profile.helpers.ProfileApplicationHelper;
-import org.eclipse.uml2.uml.Package;
-import org.eclipse.uml2.uml.Profile;
-import org.eclipse.uml2.uml.ProfileApplication;
-import org.eclipse.uml2.uml.UMLPackage;
-
-/**
- * Check if profile application is correctly duplicated on all controlled sub-packages and duplicate it if needed.
- * In case of deletion, it also removes unnecessary profile applications.
- *
- * @author vhemery
- */
-public class ProfileApplicationDuplicationChecker extends AbstractModelConstraint {
-
- /**
- * This is a result which is intended to be set with a boolean value with notification runables.
- *
- * @author vhemery
- */
- public class BooleanResult {
-
- boolean value = false;
-
- /**
- * Get result
- *
- * @return boolean result value
- */
- public boolean getValue() {
- return value;
- }
-
- /**
- * Set result
- *
- * @param pValue
- * boolean result value
- */
- public void setValue(boolean pValue) {
- value = pValue;
- }
-
- }
-
- /** Format String for a list entry */
- private static final String ENTRY_FORMAT = "<li>%s</li>";
-
- /** Constant for load all loading strategy */
- protected static final int LOAD_ALL_STRATEGY = 0;
-
- private IValidationContext lastValidatedContext = null;
-
- /**
- * Check if profile applications are correctly duplicated on controlled package and that there is no useless copy left.
- *
- * @see org.eclipse.emf.validation.AbstractModelConstraint#validate(org.eclipse.emf.validation.IValidationContext)
- *
- * @param ctx
- * validation context
- * @return validation status
- */
- @Override
- public IStatus validate(IValidationContext ctx) {
- try {
- if (ctx.equals(lastValidatedContext)) {
- return ctx.createSuccessStatus();
- } else {
- lastValidatedContext = ctx;
- }
- EObject eObject = ctx.getTarget();
- // detect profile application creation
- if ((EMFEventType.ADD.equals(ctx.getEventType()) || EMFEventType.ADD_MANY.equals(ctx.getEventType())) && ctx.getFeatureNewValue() instanceof ProfileApplication) {
- ProfileApplication profileAppl = (ProfileApplication) ctx.getFeatureNewValue();
- Package pack = (Package) eObject;
- boolean res = stereotypeApplicationAdded(pack, profileAppl);
- if (!res) {
- return ctx.createFailureStatus();
- }
- }
- // detect profile application deletion
- else if (((EMFEventType.REMOVE.equals(ctx.getEventType()) && ctx.getFeatureNewValue() instanceof ProfileApplication) || EMFEventType.REMOVE_MANY.equals(ctx.getEventType()))) {
- Map<ProfileApplication, Profile> oldAssignement = new HashMap<ProfileApplication, Profile>(ctx.getAllEvents().size());
- for (Notification n : ctx.getAllEvents()) {
- // case when profile is removed from profile application : keep the reference
- if (Notification.SET == n.getEventType() && UMLPackage.eINSTANCE.getProfileApplication_AppliedProfile().equals(n.getFeature()) && n.getNotifier() instanceof ProfileApplication) {
- Profile profile = (Profile) n.getOldValue();
- ProfileApplication profileAppl = (ProfileApplication) n.getNotifier();
- oldAssignement.put(profileAppl, profile);
- }
- // cases when profile application is removed
- if (Notification.REMOVE == n.getEventType() && n.getOldValue() instanceof ProfileApplication && n.getNotifier() instanceof Package) {
- ProfileApplication profileAppl = (ProfileApplication) n.getOldValue();
- Package pack = (Package) n.getNotifier();
- Profile profile = profileAppl.getAppliedProfile();
- if (profile == null) {
- profile = oldAssignement.get(profileAppl);
- }
- if (profile != null) {
- boolean res = stereotypeApplicationRemoved(pack, profileAppl, profile);
- if (!res) {
- return ctx.createFailureStatus();
- }
- }
- }
- }
-
- }
- return ctx.createSuccessStatus();
- } catch (RuntimeException rte) {
- // avoid throwing uncaught exception which would disable the constraint
- Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, Messages.error_during_validation, rte));
- // ensure that the constraint's failure does not prevent modification
- return ctx.createSuccessStatus();
- }
- }
-
- /**
- * Handle the case when a stereotype application is added :
- * - Inspect controlled sub-packages
- * - Duplicate profile applicationss on these
- * - Create eAnnotation for duplicated profiles
- *
- * @param packageElement
- * the package on which stereotype application has been added
- * @param profileAppl
- * the added profile application
- * @return whether addition is allowed
- */
- private boolean stereotypeApplicationAdded(Package packageElement, ProfileApplication profileAppl) {
- // Inspect controlled sub-packages
- Set<Package> controlledPack = getControlledSubPackages(packageElement);
- boolean update = checkControlledPackagesUpdateable(controlledPack);
- if (update) {
- for (Package pack : controlledPack) {
- ProfileApplicationHelper.duplicateProfileApplication(pack, profileAppl.getAppliedProfile());
- }
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Handle the case when a stereotype application is removed :
- * - Forbid direct removal of a duplicated profile application (with eAnnotation)
- * - Inspect controlled sub-packages
- * - Remove duplicated profile applications on these (with eAnnotation)
- *
- * @param packageElement
- * the package from which stereotype application has been removed
- * @param profileAppl
- * the removed profile application
- * @param profile
- * the unapplied profile
- * @return whether removal is allowed
- */
- private boolean stereotypeApplicationRemoved(Package packageElement, ProfileApplication profileAppl, Profile profile) {
- // Forbid direct removal of a duplicated profile application (with eAnnotation)
- if (ProfileApplicationHelper.isDuplicatedProfileApplication(profileAppl)) {
- Package parentPack = ProfileApplicationHelper.getParentPackageWithProfile(packageElement, profile, true);
- // restore stereotype application when it is called from parent intermediate package
- ProfileApplicationHelper.duplicateProfileApplication(packageElement, profile);
- String msg;
- if (parentPack != null) {
- msg = NLS.bind(Messages.warning_cannot_delete_duplicated, EMFCoreUtil.getQualifiedName(packageElement, true), EMFCoreUtil.getQualifiedName(parentPack, true));
- } else {
- // parent package can not be reached as it is in a different maybe not accessible resource (working on controlled resource)
- msg = NLS.bind(Messages.warning_cannot_delete_duplicated_alt, EMFCoreUtil.getQualifiedName(packageElement, true));
- }
- NotificationBuilder notifBuild = NotificationBuilder.createAsyncPopup(msg);
- notifBuild.run();
- return true;
- }
- // Inspect controlled sub-packages
- Set<Package> controlledPack = getControlledSubPackages(packageElement);
- boolean update = checkControlledPackagesUpdateable(controlledPack);
- if (update) {
- for (Package pack : controlledPack) {
- ProfileApplicationHelper.removeProfileApplicationDuplication(pack, profile, false);
- }
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Check if controlled sub-packages can be correctly updated :
- * - Check if controlled package is loaded
- * - Change the control strategy if necessary
- * - Report error if the controlled package is read-only
- *
- * @param controlledPackages
- * the controlled sub-packages (may be updated if contains proxies)
- * @return true if can be updated
- */
- private boolean checkControlledPackagesUpdateable(Set<Package> controlledPackages) {
- boolean notLoadedPackages = false;
- StringBuffer notLoadedPackagesList = new StringBuffer();
- boolean readOnlyPackages = false;
- StringBuffer readOnlyPackagesList = new StringBuffer();
- // Check if controlled package is loaded
- EditingDomain domain = null;
- for (Package pack : controlledPackages) {
- if (domain == null) {
- try {
- domain = ServiceUtilsForEObject.getInstance().getTransactionalEditingDomain(pack);
- } catch (ServiceException ex) {
- Activator.log.error(ex);
- return false;
- }
- }
-
- if (pack.eIsProxy()) {
- EObject loadedObject = domain.getResourceSet().getEObject(((InternalEObject) pack).eProxyURI(), true);
- if (loadedObject != null) {
- // pack has been reload, replace proxy;
- controlledPackages.remove(pack);
- pack = (Package) loadedObject;
- controlledPackages.add(pack);
- }
- }
- if (pack.eIsProxy()) {
- notLoadedPackages = true;
- URI uri = ((InternalEObject) pack).eProxyURI();
- String uriLastSeg = uri.lastSegment();
- String name = uriLastSeg.substring(0, uriLastSeg.length() - uri.fileExtension().length() - 1);
- String qualifName = EMFCoreUtil.getQualifiedName(pack.getOwner(), true).concat("::").concat(name);//$NON-NLS-1$
- notLoadedPackagesList.append(String.format(ENTRY_FORMAT, qualifName));
- } else {
- if (domain instanceof AdapterFactoryEditingDomain) {
- // reset read-only cache map
- ((AdapterFactoryEditingDomain) domain).getResourceToReadOnlyMap().clear();
- }
- if (domain.isReadOnly(pack.eResource())) {
- readOnlyPackages = true;
- String name = EMFCoreUtil.getQualifiedName(pack, true);
- readOnlyPackagesList.append(String.format(ENTRY_FORMAT, name));
- }
- }
- }
-
- // Report error if the controlled package is read-only
- if (readOnlyPackages) {
- String msg = NLS.bind(Messages.error_readonly, readOnlyPackagesList.toString());
- NotificationBuilder notifBuild = NotificationBuilder.createErrorPopup(msg);
- notifBuild.setHTML(true);
- notifBuild.run();
- return false;
- }
- // Change the control strategy if necessary
- if (notLoadedPackages) {
- String msg = NLS.bind(Messages.switch_loading_strategy, notLoadedPackagesList.toString());
- final BooleanResult stategyChanged = new BooleanResult();
- Runnable runStrategySwitch = new Runnable() {
-
- public void run() {
- // TODO
- StrategyChooser.setCurrentStrategy(LOAD_ALL_STRATEGY);
- stategyChanged.setValue(true);
- }
- };
- Runnable cancel = new Runnable() {
-
- public void run() {
- stategyChanged.setValue(false);
- }
- };
- NotificationBuilder notifBuild = NotificationBuilder.createYesNo(msg, runStrategySwitch, cancel);
- notifBuild.setHTML(true);
- notifBuild.setAsynchronous(false);
- notifBuild.run();
- if (stategyChanged.getValue()) {
- // refresh set controlledPackages
- return checkControlledPackagesUpdateable(controlledPackages);
- } else {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Get the controlled children packages
- *
- * @param packageElement
- * package to inspect children
- * @return set of children packages which are controlled
- */
- private Set<Package> getControlledSubPackages(Package packageElement) {
- Set<Package> controlledPackages = new HashSet<Package>();
- TreeIterator<EObject> iterator = packageElement.eAllContents();
- while (iterator.hasNext()) {
- EObject child = iterator.next();
- if (child instanceof Package) {
- // despite what AdapterFactoryEditingDomain#isControlled says, a not loaded child is controlled
- if (AdapterFactoryEditingDomain.isControlled(child) || child.eIsProxy()) {
- controlledPackages.add((Package) child);
- }
- } else {
- iterator.prune();
- }
- }
- return controlledPackages;
- }
-}
+/*****************************************************************************
+ * Copyright (c) 2011, 2016 Atos Origin, 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:
+ * Atos Origin - Initial API and implementation
+ * Christian W. Damus - bug 485220
+ *
+ *****************************************************************************/
+package org.eclipse.papyrus.uml.controlmode.profile.validation;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.emf.common.notify.Notification;
+import org.eclipse.emf.common.util.TreeIterator;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
+import org.eclipse.emf.edit.domain.EditingDomain;
+import org.eclipse.emf.validation.AbstractModelConstraint;
+import org.eclipse.emf.validation.EMFEventType;
+import org.eclipse.emf.validation.IValidationContext;
+import org.eclipse.gmf.runtime.emf.core.util.EMFCoreUtil;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.papyrus.infra.core.services.ServiceException;
+import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForEObject;
+import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForResourceSet;
+import org.eclipse.papyrus.infra.services.resourceloading.IStrategyChooser;
+import org.eclipse.papyrus.infra.widgets.toolbox.notification.builders.NotificationBuilder;
+import org.eclipse.papyrus.uml.controlmode.profile.Activator;
+import org.eclipse.papyrus.uml.controlmode.profile.Messages;
+import org.eclipse.papyrus.uml.controlmode.profile.helpers.ProfileApplicationHelper;
+import org.eclipse.uml2.uml.Package;
+import org.eclipse.uml2.uml.Profile;
+import org.eclipse.uml2.uml.ProfileApplication;
+import org.eclipse.uml2.uml.UMLPackage;
+
+/**
+ * Check if profile application is correctly duplicated on all controlled sub-packages and duplicate it if needed.
+ * In case of deletion, it also removes unnecessary profile applications.
+ *
+ * @author vhemery
+ */
+public class ProfileApplicationDuplicationChecker extends AbstractModelConstraint {
+
+ /** Format String for a list entry */
+ private static final String ENTRY_FORMAT = "<li>%s</li>";
+
+ /** Constant for load all loading strategy */
+ protected static final int LOAD_ALL_STRATEGY = 0;
+
+ private IValidationContext lastValidatedContext = null;
+
+ /**
+ * Check if profile applications are correctly duplicated on controlled package and that there is no useless copy left.
+ *
+ * @see org.eclipse.emf.validation.AbstractModelConstraint#validate(org.eclipse.emf.validation.IValidationContext)
+ *
+ * @param ctx
+ * validation context
+ * @return validation status
+ */
+ @Override
+ public IStatus validate(IValidationContext ctx) {
+ try {
+ if (ctx.equals(lastValidatedContext)) {
+ return ctx.createSuccessStatus();
+ } else {
+ lastValidatedContext = ctx;
+ }
+ EObject eObject = ctx.getTarget();
+ // detect profile application creation
+ if ((EMFEventType.ADD.equals(ctx.getEventType()) || EMFEventType.ADD_MANY.equals(ctx.getEventType())) && ctx.getFeatureNewValue() instanceof ProfileApplication) {
+ ProfileApplication profileAppl = (ProfileApplication) ctx.getFeatureNewValue();
+ Package pack = (Package) eObject;
+ boolean res = stereotypeApplicationAdded(pack, profileAppl);
+ if (!res) {
+ return ctx.createFailureStatus();
+ }
+ }
+ // detect profile application deletion
+ else if (((EMFEventType.REMOVE.equals(ctx.getEventType()) && ctx.getFeatureNewValue() instanceof ProfileApplication) || EMFEventType.REMOVE_MANY.equals(ctx.getEventType()))) {
+ Map<ProfileApplication, Profile> oldAssignement = new HashMap<ProfileApplication, Profile>(ctx.getAllEvents().size());
+ for (Notification n : ctx.getAllEvents()) {
+ // case when profile is removed from profile application : keep the reference
+ if (Notification.SET == n.getEventType() && UMLPackage.eINSTANCE.getProfileApplication_AppliedProfile().equals(n.getFeature()) && n.getNotifier() instanceof ProfileApplication) {
+ Profile profile = (Profile) n.getOldValue();
+ ProfileApplication profileAppl = (ProfileApplication) n.getNotifier();
+ oldAssignement.put(profileAppl, profile);
+ }
+ // cases when profile application is removed
+ if (Notification.REMOVE == n.getEventType() && n.getOldValue() instanceof ProfileApplication && n.getNotifier() instanceof Package) {
+ ProfileApplication profileAppl = (ProfileApplication) n.getOldValue();
+ Package pack = (Package) n.getNotifier();
+ Profile profile = profileAppl.getAppliedProfile();
+ if (profile == null) {
+ profile = oldAssignement.get(profileAppl);
+ }
+ if (profile != null) {
+ boolean res = stereotypeApplicationRemoved(pack, profileAppl, profile);
+ if (!res) {
+ return ctx.createFailureStatus();
+ }
+ }
+ }
+ }
+
+ }
+ return ctx.createSuccessStatus();
+ } catch (RuntimeException rte) {
+ // avoid throwing uncaught exception which would disable the constraint
+ Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, Messages.error_during_validation, rte));
+ // ensure that the constraint's failure does not prevent modification
+ return ctx.createSuccessStatus();
+ }
+ }
+
+ /**
+ * Handle the case when a stereotype application is added :
+ * - Inspect controlled sub-packages
+ * - Duplicate profile applicationss on these
+ * - Create eAnnotation for duplicated profiles
+ *
+ * @param packageElement
+ * the package on which stereotype application has been added
+ * @param profileAppl
+ * the added profile application
+ * @return whether addition is allowed
+ */
+ private boolean stereotypeApplicationAdded(Package packageElement, ProfileApplication profileAppl) {
+ // Inspect controlled sub-packages
+ Set<Package> controlledPack = getControlledSubPackages(packageElement);
+ boolean update = checkControlledPackagesUpdateable(controlledPack);
+ if (update) {
+ for (Package pack : controlledPack) {
+ ProfileApplicationHelper.duplicateProfileApplication(pack, profileAppl.getAppliedProfile());
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Handle the case when a stereotype application is removed :
+ * - Forbid direct removal of a duplicated profile application (with eAnnotation)
+ * - Inspect controlled sub-packages
+ * - Remove duplicated profile applications on these (with eAnnotation)
+ *
+ * @param packageElement
+ * the package from which stereotype application has been removed
+ * @param profileAppl
+ * the removed profile application
+ * @param profile
+ * the unapplied profile
+ * @return whether removal is allowed
+ */
+ private boolean stereotypeApplicationRemoved(Package packageElement, ProfileApplication profileAppl, Profile profile) {
+ // Forbid direct removal of a duplicated profile application (with eAnnotation)
+ if (ProfileApplicationHelper.isDuplicatedProfileApplication(profileAppl)) {
+ Package parentPack = ProfileApplicationHelper.getParentPackageWithProfile(packageElement, profile, true);
+ // restore stereotype application when it is called from parent intermediate package
+ ProfileApplicationHelper.duplicateProfileApplication(packageElement, profile);
+ String msg;
+ if (parentPack != null) {
+ msg = NLS.bind(Messages.warning_cannot_delete_duplicated, EMFCoreUtil.getQualifiedName(packageElement, true), EMFCoreUtil.getQualifiedName(parentPack, true));
+ } else {
+ // parent package can not be reached as it is in a different maybe not accessible resource (working on controlled resource)
+ msg = NLS.bind(Messages.warning_cannot_delete_duplicated_alt, EMFCoreUtil.getQualifiedName(packageElement, true));
+ }
+ NotificationBuilder notifBuild = NotificationBuilder.createAsyncPopup(msg);
+ notifBuild.run();
+ return true;
+ }
+ // Inspect controlled sub-packages
+ Set<Package> controlledPack = getControlledSubPackages(packageElement);
+ boolean update = checkControlledPackagesUpdateable(controlledPack);
+ if (update) {
+ for (Package pack : controlledPack) {
+ ProfileApplicationHelper.removeProfileApplicationDuplication(pack, profile, false);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Check if controlled sub-packages can be correctly updated :
+ * - Check if controlled package is loaded
+ * - Change the control strategy if necessary
+ * - Report error if the controlled package is read-only
+ *
+ * @param controlledPackages
+ * the controlled sub-packages (may be updated if contains proxies)
+ * @return true if can be updated
+ */
+ private boolean checkControlledPackagesUpdateable(Set<Package> controlledPackages) {
+ boolean notLoadedPackages = false;
+ StringBuffer notLoadedPackagesList = new StringBuffer();
+ boolean readOnlyPackages = false;
+ StringBuffer readOnlyPackagesList = new StringBuffer();
+ // Check if controlled package is loaded
+ EditingDomain domain = null;
+ for (Package pack : controlledPackages) {
+ if (domain == null) {
+ try {
+ domain = ServiceUtilsForEObject.getInstance().getTransactionalEditingDomain(pack);
+ } catch (ServiceException ex) {
+ Activator.log.error(ex);
+ return false;
+ }
+ }
+
+ if (pack.eIsProxy()) {
+ EObject loadedObject = domain.getResourceSet().getEObject(((InternalEObject) pack).eProxyURI(), true);
+ if (loadedObject != null) {
+ // pack has been reload, replace proxy;
+ controlledPackages.remove(pack);
+ pack = (Package) loadedObject;
+ controlledPackages.add(pack);
+ }
+ }
+ if (pack.eIsProxy()) {
+ notLoadedPackages = true;
+ URI uri = ((InternalEObject) pack).eProxyURI();
+ String uriLastSeg = uri.lastSegment();
+ String name = uriLastSeg.substring(0, uriLastSeg.length() - uri.fileExtension().length() - 1);
+ String qualifName = EMFCoreUtil.getQualifiedName(pack.getOwner(), true).concat("::").concat(name);//$NON-NLS-1$
+ notLoadedPackagesList.append(String.format(ENTRY_FORMAT, qualifName));
+ } else {
+ if (domain instanceof AdapterFactoryEditingDomain) {
+ // reset read-only cache map
+ ((AdapterFactoryEditingDomain) domain).getResourceToReadOnlyMap().clear();
+ }
+ if (domain.isReadOnly(pack.eResource())) {
+ readOnlyPackages = true;
+ String name = EMFCoreUtil.getQualifiedName(pack, true);
+ readOnlyPackagesList.append(String.format(ENTRY_FORMAT, name));
+ }
+ }
+ }
+
+ // Report error if the controlled package is read-only
+ if (readOnlyPackages) {
+ String msg = NLS.bind(Messages.error_readonly, readOnlyPackagesList.toString());
+ NotificationBuilder notifBuild = NotificationBuilder.createErrorPopup(msg);
+ notifBuild.setHTML(true);
+ notifBuild.run();
+ return false;
+ }
+ // Change the control strategy if necessary
+ if (notLoadedPackages) {
+ String msg = NLS.bind(Messages.switch_loading_strategy, notLoadedPackagesList.toString());
+ final AtomicBoolean stategyChanged = new AtomicBoolean();
+
+ try {
+ final IStrategyChooser strategyChooser = ServiceUtilsForResourceSet.getInstance().getService(IStrategyChooser.class, domain.getResourceSet());
+ Runnable runStrategySwitch = new Runnable() {
+
+ public void run() {
+ // TODO
+ stategyChanged.set(strategyChooser.setStrategy(LOAD_ALL_STRATEGY));
+ }
+ };
+ Runnable cancel = new Runnable() {
+
+ public void run() {
+ stategyChanged.set(false);
+ }
+ };
+ NotificationBuilder notifBuild = NotificationBuilder.createYesNo(msg, runStrategySwitch, cancel);
+ notifBuild.setHTML(true);
+ notifBuild.setAsynchronous(false);
+ notifBuild.run();
+ } catch (ServiceException e) {
+ Activator.log.error(e);
+ }
+
+ if (stategyChanged.get()) {
+ // refresh set controlledPackages
+ return checkControlledPackagesUpdateable(controlledPackages);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get the controlled children packages
+ *
+ * @param packageElement
+ * package to inspect children
+ * @return set of children packages which are controlled
+ */
+ private Set<Package> getControlledSubPackages(Package packageElement) {
+ Set<Package> controlledPackages = new HashSet<Package>();
+ TreeIterator<EObject> iterator = packageElement.eAllContents();
+ while (iterator.hasNext()) {
+ EObject child = iterator.next();
+ if (child instanceof Package) {
+ // despite what AdapterFactoryEditingDomain#isControlled says, a not loaded child is controlled
+ if (AdapterFactoryEditingDomain.isControlled(child) || child.eIsProxy()) {
+ controlledPackages.add((Package) child);
+ }
+ } else {
+ iterator.prune();
+ }
+ }
+ return controlledPackages;
+ }
+}

Back to the top