org.eclipse.sirius.business.internal.session.SessionEventBrokerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.sirius.business.internal.session.SessionEventBrokerImpl.java

Source

/*******************************************************************************
 * Copyright (c) 2010 THALES GLOBAL SERVICES.
 * 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.business.internal.session;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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.ecore.EStructuralFeature;
import org.eclipse.emf.transaction.NotificationFilter;
import org.eclipse.emf.transaction.ResourceSetChangeEvent;
import org.eclipse.emf.transaction.ResourceSetListenerImpl;
import org.eclipse.emf.transaction.RollbackException;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.sirius.business.api.session.ModelChangeTrigger;
import org.eclipse.sirius.business.api.session.SessionEventBroker;
import org.eclipse.sirius.ext.base.Option;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;

/**
 * This class keeps track of registered listeners and notify them when something
 * did change on the elements they are listening. It has pretty much the same
 * focus as the GMF DiagramEventBroker except it's not GMF's dependent.
 * 
 * @author cbrun
 * 
 */
public class SessionEventBrokerImpl extends ResourceSetListenerImpl implements SessionEventBroker {

    private static Function<ModelChangeTrigger, Integer> getPriorityFunction = new Function<ModelChangeTrigger, Integer>() {

        @Override
        public Integer apply(ModelChangeTrigger from) {
            return Integer.valueOf(from.priority());
        }
    };

    private TransactionalEditingDomain domain;

    private Multimap<EObject, ModelChangeTrigger> eObjectsToListeners = HashMultimap.create();

    private Map<EStructuralFeature, Multimap<EObject, ModelChangeTrigger>> featuresToListeners = Maps.newHashMap();

    private Multimap<NotificationFilter, ModelChangeTrigger> scopedTriggers = HashMultimap.create();

    /**
     * create a new broker.
     * 
     * @param domain
     *            the editing domain to listen.
     */
    public SessionEventBrokerImpl(TransactionalEditingDomain domain) {
        super(NotificationFilter.NOT_TOUCH);
        this.domain = domain;
        domain.addResourceSetListener(this);
    }

    /**
     * Converts a Guava predicate on EMF Notification into a NotificationFilter
     * as expected by the
     * {@link #addLocalTrigger(NotificationFilter, ModelChangeTrigger)} method.
     * 
     * @param pred
     *            a Guava predicate on EMF notifications.
     * @return an equivalent NotificationFilter.
     */
    public static NotificationFilter asFilter(final Predicate<Notification> pred) {
        return new NotificationFilter.Custom() {
            @Override
            public boolean matches(Notification notification) {
                return pred.apply(notification);
            }
        };
    }

    @Override
    public void addLocalTrigger(EObject element, EStructuralFeature feature, ModelChangeTrigger trigger) {
        Multimap<EObject, ModelChangeTrigger> listeners = featuresToListeners.get(feature);
        if (listeners == null) {
            listeners = HashMultimap.create();
            featuresToListeners.put(feature, listeners);
        }
        listeners.put(element, trigger);

    }

    @Override
    public void addLocalTrigger(EObject element, ModelChangeTrigger trigger) {
        eObjectsToListeners.put(element, trigger);
    }

    private void collectListeners(Notification msg, EObject changedObj, Multimap<EObject, ModelChangeTrigger> map,
            Multimap<ModelChangeTrigger, Notification> result) {
        Collection<ModelChangeTrigger> listeners = map.get(changedObj);
        if (listeners != null) {
            for (ModelChangeTrigger preCommitListener : listeners) {
                result.put(preCommitListener, msg);
            }
        }
    }

    private void collectScopedListeners(final Notification msg, final EObject changedObj,
            Multimap<ModelChangeTrigger, Notification> result) {
        Iterable<NotificationFilter> filteredScoped = Iterables.filter(scopedTriggers.keys(),
                new Predicate<NotificationFilter>() {
                    @Override
                    public boolean apply(NotificationFilter input) {
                        return input.matches(msg);
                    }
                });
        for (NotificationFilter predicate : filteredScoped) {
            Collection<ModelChangeTrigger> triggersValidForThisNotification = scopedTriggers.get(predicate);
            if (triggersValidForThisNotification != null) {
                for (ModelChangeTrigger modelChangeTrigger : triggersValidForThisNotification) {
                    result.put(modelChangeTrigger, msg);
                }
            }

        }
    }

    @Override
    public void addLocalTrigger(NotificationFilter scope, ModelChangeTrigger trigger) {
        if (!scopedTriggers.containsEntry(scope, trigger)) {
            scopedTriggers.put(scope, trigger);
        }
    }

    private Multimap<ModelChangeTrigger, Notification> collectListenersToNotify(List<Notification> notifications) {
        Multimap<ModelChangeTrigger, Notification> result = HashMultimap.create();
        for (Notification msg : notifications) {
            if (msg.getNotifier() instanceof EObject) {
                EObject changedObj = (EObject) msg.getNotifier();
                collectListeners(msg, changedObj, eObjectsToListeners, result);
                collectScopedListeners(msg, changedObj, result);
                if (msg.getFeature() instanceof EStructuralFeature) {
                    Multimap<EObject, ModelChangeTrigger> eObhectsToListenersMap = featuresToListeners
                            .get(msg.getFeature());
                    if (eObhectsToListenersMap != null) {
                        collectListeners(msg, changedObj, eObhectsToListenersMap, result);
                    }
                }

            }

        }
        return result;
    }

    @Override
    public boolean isAggregatePrecommitListener() {
        return true;
    }

    @Override
    public boolean isPrecommitOnly() {
        return true;
    }

    private void removeListenerFromMap(ModelChangeTrigger listenerToRemove, Multimap<?, ModelChangeTrigger> map) {
        Iterator<ModelChangeTrigger> it = map.values().iterator();
        while (it.hasNext()) {
            ModelChangeTrigger cur = it.next();
            if (cur == listenerToRemove) {
                it.remove();
            }
        }
    }

    @Override
    public void removeLocalTrigger(ModelChangeTrigger listenerToRemove) {
        removeListenerFromMap(listenerToRemove, eObjectsToListeners);
        for (Multimap<EObject, ModelChangeTrigger> map : featuresToListeners.values()) {
            removeListenerFromMap(listenerToRemove, map);
        }
        removeListenerFromMap(listenerToRemove, scopedTriggers);
    }

    @Override
    public Command transactionAboutToCommit(ResourceSetChangeEvent event) throws RollbackException {
        CompoundCommand compoundCommand = new CompoundCommand();
        Multimap<ModelChangeTrigger, Notification> listenersToNotify = collectListenersToNotify(
                event.getNotifications());
        if (listenersToNotify != null && !listenersToNotify.isEmpty()) {
            Ordering<ModelChangeTrigger> priorityOrdering = Ordering.natural()
                    .onResultOf(SessionEventBrokerImpl.getPriorityFunction);
            List<ModelChangeTrigger> sortedKeys = priorityOrdering.sortedCopy(listenersToNotify.keySet());
            for (ModelChangeTrigger key : sortedKeys) {
                Collection<Notification> notif = listenersToNotify.get(key);
                if (notif != null && !notif.isEmpty()) {
                    Option<Command> triggerCmdOption = key.localChangesAboutToCommit(notif);
                    if (triggerCmdOption.some() && triggerCmdOption.get().canExecute()) {
                        compoundCommand.append(triggerCmdOption.get());
                    }
                }
            }
        }
        return compoundCommand;
    }

    @Override
    public void dispose() {
        domain.removeResourceSetListener(this);
        eObjectsToListeners.clear();
        featuresToListeners.clear();
        scopedTriggers.clear();
    }

}