org.eclipse.incquery.runtime.internal.engine.ModelUpdateProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.incquery.runtime.internal.engine.ModelUpdateProvider.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro
 * 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:
 *   Abel Hegedus - initial API and implementation
 *******************************************************************************/
package org.eclipse.incquery.runtime.internal.engine;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.log4j.Logger;
import org.eclipse.incquery.runtime.api.AdvancedIncQueryEngine;
import org.eclipse.incquery.runtime.api.IMatchUpdateListener;
import org.eclipse.incquery.runtime.api.IPatternMatch;
import org.eclipse.incquery.runtime.api.IncQueryEngineLifecycleListener;
import org.eclipse.incquery.runtime.api.IncQueryMatcher;
import org.eclipse.incquery.runtime.api.IncQueryModelUpdateListener;
import org.eclipse.incquery.runtime.api.IncQueryModelUpdateListener.ChangeLevel;
import org.eclipse.incquery.runtime.api.scope.IncQueryBaseIndexChangeListener;
import org.eclipse.incquery.runtime.exception.IncQueryException;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;

public final class ModelUpdateProvider extends ListenerContainer<IncQueryModelUpdateListener> {

    private final AdvancedIncQueryEngine incQueryEngine;
    private ChangeLevel currentChange = ChangeLevel.NO_CHANGE;
    private ChangeLevel maxLevel = ChangeLevel.NO_CHANGE;
    private final Multimap<ChangeLevel, IncQueryModelUpdateListener> listenerMap;
    private final Logger logger;

    public ModelUpdateProvider(AdvancedIncQueryEngine incQueryEngine, Logger logger) {
        super();
        this.incQueryEngine = incQueryEngine;
        this.logger = logger;
        Map<ChangeLevel, Collection<IncQueryModelUpdateListener>> map = Maps.newEnumMap(ChangeLevel.class);
        listenerMap = Multimaps.newSetMultimap(map,
                new com.google.common.base.Supplier<Set<IncQueryModelUpdateListener>>() {
                    @Override
                    public Set<IncQueryModelUpdateListener> get() {
                        return Sets.newHashSet();
                    }
                });
    }

    @Override
    protected void listenerAdded(IncQueryModelUpdateListener listener) {
        // check ChangeLevel
        // create callback for given level if required
        if (listenerMap.isEmpty()) {
            try {
                this.incQueryEngine.getBaseIndex().addBaseIndexChangeListener(indexListener);
                // add listener to new matchers (use lifecycle listener)
                this.incQueryEngine.addLifecycleListener(selfListener);
            } catch (IncQueryException e) {
                throw new IllegalStateException("Model update listener used on engine without base index", e);
            }
        }

        ChangeLevel changeLevel = listener.getLevel();
        listenerMap.put(changeLevel, listener);
        // increase or keep max level of listeners
        ChangeLevel oldMaxLevel = maxLevel;
        maxLevel = maxLevel.changeOccured(changeLevel);
        if (!maxLevel.equals(oldMaxLevel) && ChangeLevel.MATCHSET.compareTo(oldMaxLevel) > 0
                && ChangeLevel.MATCHSET.compareTo(maxLevel) <= 0) {
            // add matchUpdateListener to all matchers
            for (IncQueryMatcher<?> matcher : this.incQueryEngine.getCurrentMatchers()) {
                this.incQueryEngine.addMatchUpdateListener(matcher, matchSetListener, false);
            }
        }
    }

    @Override
    protected void listenerRemoved(IncQueryModelUpdateListener listener) {
        ChangeLevel changeLevel = listener.getLevel();
        boolean removed = listenerMap.remove(changeLevel, listener);
        if (!removed) {
            handleUnsuccesfulRemove(listener);
        }

        updateMaxLevel();

        if (listenerMap.isEmpty()) {
            this.incQueryEngine.removeLifecycleListener(selfListener);
            removeBaseIndexChangeListener();
        }
    }

    private void removeBaseIndexChangeListener() {
        try {
            this.incQueryEngine.getBaseIndex().removeBaseIndexChangeListener(indexListener);
        } catch (IncQueryException e) {
            throw new IllegalStateException("Model update listener used on engine without base index", e);
        }
    }

    private void updateMaxLevel() {
        if (!listenerMap.containsKey(maxLevel)) {
            ChangeLevel newMaxLevel = ChangeLevel.NO_CHANGE;
            for (ChangeLevel level : ImmutableSet.copyOf(listenerMap.keySet())) {
                newMaxLevel = newMaxLevel.changeOccured(level);
            }
            maxLevel = newMaxLevel;
        }
        if (maxLevel.compareTo(ChangeLevel.MATCHSET) < 0) {
            // remove listener from matchers
            for (IncQueryMatcher<?> matcher : this.incQueryEngine.getCurrentMatchers()) {
                this.incQueryEngine.removeMatchUpdateListener(matcher, matchSetListener);
            }
        }
    }

    private void handleUnsuccesfulRemove(IncQueryModelUpdateListener listener) {
        for (Entry<ChangeLevel, IncQueryModelUpdateListener> entry : listenerMap.entries()) {
            if (entry.getValue().equals(listener)) {
                logger.error("Listener " + listener + " change level changed since initialization!");
                listenerMap.remove(entry.getKey(), entry.getValue());
                return; // listener is contained only once
            }
        }
        logger.error("Listener " + listener + " already removed from map (e.g. engine was already disposed)!");
    }

    private void notifyListeners() {

        // any change that occurs after this point should be regarded as a new event
        // FIXME what should happen when a listener creates new notifications?
        // -> other listeners will get events in different order
        ChangeLevel tempLevel = currentChange;
        currentChange = ChangeLevel.NO_CHANGE;

        if (!listenerMap.isEmpty()) {
            for (ChangeLevel level : ImmutableSet.copyOf(listenerMap.keySet())) {
                if (tempLevel.compareTo(level) >= 0) {
                    for (IncQueryModelUpdateListener listener : new ArrayList<IncQueryModelUpdateListener>(
                            listenerMap.get(level))) {
                        try {
                            listener.notifyChanged(tempLevel);
                        } catch (Exception ex) {
                            logger.error(
                                    "IncQuery encountered an error in delivering model update notification to listener "
                                            + listener + ".",
                                    ex);
                        }
                    }
                }
            }
        } else {
            throw new IllegalStateException(
                    "Notify listeners must not be called without listeners! Maybe an update callback was not removed correctly.");
        }

    }

    // model update "providers":
    // - model: IQBase callback even if not dirty
    // - index: IQBase dirty callback
    private final IncQueryBaseIndexChangeListener indexListener = new IncQueryBaseIndexChangeListener() {

        public boolean onlyOnIndexChange() {
            return false;
        }

        public void notifyChanged(boolean indexChanged) {
            if (indexChanged) {
                currentChange = currentChange.changeOccured(ChangeLevel.INDEX);
            } else {
                currentChange = currentChange.changeOccured(ChangeLevel.MODEL);
            }
            notifyListeners();
        }

    };
    // - matchset: add the same listener to each matcher and use a dirty flag. needs IQBase callback as well
    private final IMatchUpdateListener<IPatternMatch> matchSetListener = new IMatchUpdateListener<IPatternMatch>() {

        @Override
        public void notifyDisappearance(IPatternMatch match) {
            currentChange = currentChange.changeOccured(ChangeLevel.MATCHSET);
        }

        @Override
        public void notifyAppearance(IPatternMatch match) {
            currentChange = currentChange.changeOccured(ChangeLevel.MATCHSET);
        }
    };

    private final IncQueryEngineLifecycleListener selfListener = new IncQueryEngineLifecycleListener() {

        @Override
        public void matcherInstantiated(IncQueryMatcher<? extends IPatternMatch> matcher) {
            if (maxLevel.compareTo(ChangeLevel.MATCHSET) >= 0) {
                ModelUpdateProvider.this.incQueryEngine.addMatchUpdateListener(matcher, matchSetListener, false);
            }
        }

        @Override
        public void engineWiped() {
        }

        @Override
        public void engineDisposed() {
            removeBaseIndexChangeListener();
            listenerMap.clear();
            maxLevel = ChangeLevel.NO_CHANGE;
        }

        @Override
        public void engineBecameTainted(String description, Throwable t) {
        }
    };
}