blob: 057ba249a53ffe2f4634f625805e044a87bc1082 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2016 THALES GLOBAL SERVICES 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.tools.api.ui;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.transaction.NotificationFilter;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.sirius.business.api.dialect.command.RefreshRepresentationsCommand;
import org.eclipse.sirius.business.api.preferences.SiriusPreferencesKeys;
import org.eclipse.sirius.business.api.session.ModelChangeTrigger;
import org.eclipse.sirius.business.api.session.SessionListener;
import org.eclipse.sirius.business.internal.dialect.command.RefreshImpactedElementsCommand;
import org.eclipse.sirius.business.internal.session.danalysis.DanglingRefRemovalTrigger;
import org.eclipse.sirius.ext.base.Option;
import org.eclipse.sirius.ext.base.Options;
import org.eclipse.sirius.tools.api.command.ui.RefreshFilterManager;
import org.eclipse.sirius.tools.internal.ui.RefreshHelper;
import org.eclipse.sirius.viewpoint.DRepresentation;
import org.eclipse.sirius.viewpoint.DSemanticDecorator;
import org.eclipse.sirius.viewpoint.SiriusPlugin;
import com.google.common.collect.Lists;
/**
* A listener to refresh all Sirius opened editors. It is used as :
* <UL>
* <LI>precommit listener (a model change trigger of the
* {@link org.eclipse.sirius.business.api.session.SessionEventBroker}) : on
* semantic modifications (with localChangesAboutToCommit())</LI>
* <LI>session listener : on reloading of resources detecting by the session
* itself (with notify())</LI>
* </UL>
*
* @author <a href="mailto:laurent.redor@obeo.fr">Laurent Redor</a>
*/
public class RefreshEditorsPrecommitListener implements ModelChangeTrigger, SessionListener {
/**
* Priority of this {@link ModelChangeTrigger}.
*/
public static final int REFRESH_EDITOR_PRIORITY = DanglingRefRemovalTrigger.DANGLING_REFERENCE_REMOVAL_PRIORITY + 1;
/**
* Filter {@link Notification}s which are not touch. More filtering work is
* done later in localChangesAboutToCommit, see
* isImpactingNotification(Collection<Notification>) which return true as
* soon as an impacting notification is found. This is not done here for
* performance reason: we need the container resource of the notifier.
*/
public static final NotificationFilter IS_IMPACTING = new NotificationFilter.Custom() {
@Override
public boolean matches(Notification notification) {
return !notification.isTouch();
}
};
/**
* The editing domain used to create the refresh command.
*/
TransactionalEditingDomain transactionalEditingDomain;
/**
* True if this listener must launch a refresh even if the autoRefresh is
* off.
*/
private boolean forceRefresh;
/**
* Representations to refresh even if any editors is opened on it.
*/
private final Collection<DRepresentation> representationsToForceRefresh = new ArrayList<DRepresentation>();
/**
* A list of {@link PostRefreshCommandFactory} that is called to complete
* the refresh command. The commands provided by the factory is added after
* the refresh command if it can be executed.
*/
private final Collection<PostRefreshCommandFactory> postRefreshCommandFactories = new ArrayList<PostRefreshCommandFactory>();
private boolean disabled;
/**
* Default constructor.
*
* @param transactionalEditingDomain
* The editing domain used to create the refresh command.
*/
public RefreshEditorsPrecommitListener(TransactionalEditingDomain transactionalEditingDomain) {
this.transactionalEditingDomain = transactionalEditingDomain;
}
@Override
public int priority() {
return REFRESH_EDITOR_PRIORITY;
}
/**
* {@inheritDoc}
*
* Do a refresh only if there is at least one notification that concern
* another thing that an aird Resource.
*/
@Override
public Option<Command> localChangesAboutToCommit(Collection<Notification> notifications) {
Command result = null;
if (!disabled) {
if (needsRefresh()) {
boolean impactingNotification = RefreshHelper.isImpactingNotification(notifications);
// Do nothing if the notification concern only elements of aird
// resource and that the representationsToForceRefresh is empty.
if (impactingNotification || !representationsToForceRefresh.isEmpty()) {
Option<? extends Command> optionCommand = getRefreshOpenedRepresentationsCommand(impactingNotification);
if (optionCommand.some()) {
result = optionCommand.get();
}
}
setForceRefresh(false);
representationsToForceRefresh.clear();
} else if (RefreshHelper.isImpactingNotification(notifications)) {
Option<? extends Command> optionCommand = getRefreshImpactedElementsCommandForOpenedRepresentations(notifications);
if (optionCommand.some()) {
result = optionCommand.get();
}
}
}
disabled = false;
return Options.newSome(result);
}
/**
* Compute the refresh command or null if no refresh is needed.
*
* @param isChanged
* True if there is some semantic changes or a reloading of the
* session, false otherwise
* @return An optional command if at least one refresh is needed.
*/
private Option<? extends Command> getRefreshOpenedRepresentationsCommand(boolean isChanged) {
// Get all Sirius editors (that respects the
// DialectEditor interface) */
Option<? extends Command> result = Options.newNone();
Collection<DRepresentation> representationsToRefresh = new LinkedHashSet<DRepresentation>();
// if ForceRefresh is activate and automaticRefresh is disable, only the current diagram is refreshed.
if (isChanged && isAutoRefresh()) {
representationsToRefresh.addAll(RefreshFilterManager.INSTANCE.getOpenedRepresantationsToRefresh());
}
representationsToRefresh.addAll(representationsToForceRefresh);
// Refresh only the editors of the current editing domain.
restrictRepresentationWithinCurrentEditingDomain(representationsToRefresh);
if (!representationsToRefresh.isEmpty()) {
CompoundCommand cc = new CompoundCommand();
cc.append(new RefreshRepresentationsCommand(transactionalEditingDomain, new NullProgressMonitor(), representationsToRefresh));
// Additionnal commands ?
for (PostRefreshCommandFactory listener : postRefreshCommandFactories) {
cc.appendIfCanExecute(listener.getPostCommandToExecute(transactionalEditingDomain, representationsToRefresh));
}
postRefreshCommandFactories.clear();
result = Options.newSome(cc);
}
return result;
}
private void restrictRepresentationWithinCurrentEditingDomain(Collection<DRepresentation> representationsToRefresh) {
for (DRepresentation rep : Lists.newArrayList(representationsToRefresh)) {
if (transactionalEditingDomain != TransactionUtil.getEditingDomain(rep)) {
representationsToRefresh.remove(rep);
} else if (rep instanceof DSemanticDecorator) {
DSemanticDecorator dSemanticDecorator = (DSemanticDecorator) rep;
EObject target = dSemanticDecorator.getTarget();
if (target != null && transactionalEditingDomain != TransactionUtil.getEditingDomain(target)) {
representationsToRefresh.remove(rep);
}
}
}
}
private Option<? extends Command> getRefreshImpactedElementsCommandForOpenedRepresentations(Collection<Notification> notifications) {
Option<? extends Command> result = Options.newNone();
Collection<DRepresentation> representationsToRefresh = new LinkedHashSet<DRepresentation>();
representationsToRefresh.addAll(RefreshFilterManager.INSTANCE.getOpenedRepresantationsToRefresh());
restrictRepresentationWithinCurrentEditingDomain(representationsToRefresh);
if (!representationsToRefresh.isEmpty()) {
CompoundCommand cc = new CompoundCommand();
cc.append(new RefreshImpactedElementsCommand(transactionalEditingDomain, new NullProgressMonitor(), representationsToRefresh, notifications));
result = Options.newSome(cc);
}
return result;
}
/**
* Return the actual forceRefresh status.
*
* @return the forceRefresh
*/
public boolean isForceRefresh() {
return forceRefresh;
}
/**
* Force this PrecommitListener to launch a refresh the next time it is
* called.
*
* @param forceRefresh
* the forceRefresh to set
*/
public void setForceRefresh(boolean forceRefresh) {
this.forceRefresh = forceRefresh;
}
private boolean needsRefresh() {
return (isForceRefresh() || isAutoRefresh()) && !(RefreshFilterManager.INSTANCE.getOpenedRepresantationsToRefresh().isEmpty() && representationsToForceRefresh.isEmpty());
}
/**
* Check if someone notify me for a force refresh or if the preference
* AutoRefresh is on.
*
* @return true if a refresh must be launch.
*/
private boolean isAutoRefresh() {
return Platform.isRunning() && Platform.getPreferencesService().getBoolean(SiriusPlugin.ID, SiriusPreferencesKeys.PREF_AUTO_REFRESH.name(), false, null);
}
@Override
public void notify(int changeKind) {
if (SessionListener.REPLACED == changeKind) {
if (needsRefresh()) {
setForceRefresh(false);
// The session has detected a reload of a resource (possibly
// because
// of an outside modification of the resource) so we must also
// launch a refresh.
Option<? extends Command> optionCommand = getRefreshOpenedRepresentationsCommand(true);
representationsToForceRefresh.clear();
if (optionCommand.some()) {
transactionalEditingDomain.getCommandStack().execute(optionCommand.get());
}
}
}
}
/**
* Add a representation to refresh even if no editor is opened on it.
*
* @param representation
* Representation to add.
*/
public void addRepresentationToForceRefresh(DRepresentation representation) {
if (!representationsToForceRefresh.contains(representation)) {
representationsToForceRefresh.add(representation);
}
}
/**
* Add a new PostRefreshCommandFactory to the
* RefreshEditorsPrecommitListener. The commands provided by the factory is
* added after the refresh command if it can be executed.
*
* The factory is removed after the first refresh.
*
* @param factory
* new PostRefreshCommandFactory to add.
*/
public void addPostRefreshCommandFactory(final PostRefreshCommandFactory factory) {
postRefreshCommandFactories.add(factory);
}
/**
* Disable the next refresh attempt in the current precommit loop iteration.
*/
public void disable() {
disabled = true;
}
}