aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/view/AbstractRegistryView.java
blob: 7b3c34bacca4846738c0013018c52a7b38ff187d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*******************************************************************************
 * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-v20.html.
 * 
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/
package tools.refinery.viatra.runtime.registry.view;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.log4j.Logger;
import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
import tools.refinery.viatra.runtime.matchers.util.Preconditions;
import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistry;
import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryChangeListener;
import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryEntry;
import tools.refinery.viatra.runtime.registry.IRegistryView;
import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil;

/**
 * An abstract {@link IRegistryView} implementation that stores the registry, the set of listeners added to the view and
 * the FQN to entry map of the view itself. The only responsibility of subclasses is to decide whether an entry received
 * as an addition or removal notification is relevant to the view.
 * 
 * @author Abel Hegedus
 * @since 1.3
 */
public abstract class AbstractRegistryView implements IRegistryView {

    private static final String LISTENER_EXCEPTION_REMOVE = "Exception occurred while notifying view listener %s about entry removal";
    private static final String LISTENER_EXCEPTION_ADD = "Exception occurred while notifying view listener %s about entry addition";
    protected final IQuerySpecificationRegistry registry;
    protected final IMultiLookup<String, IQuerySpecificationRegistryEntry> fqnToEntryMap;
    protected final Set<IQuerySpecificationRegistryChangeListener> listeners;
    protected final boolean allowDuplicateFQNs;

    /**
     * This method is called both when an addition or removal notification is received from the registry. Subclasses can
     * implement view filtering by returning false for those specifications that are not relevant for this view.
     * 
     * @param entry
     *            that is added or removed in the registry
     * @return true if the entry should be added to or removed from the view, false otherwise
     */
    protected abstract boolean isEntryRelevant(IQuerySpecificationRegistryEntry entry);

    /**
     * Creates a new view instance for the given registry. Note that views are created by the registry and the view
     * update mechanisms are also set up by the registry.
     * 
     * @param registry
     */
    public AbstractRegistryView(IQuerySpecificationRegistry registry, boolean allowDuplicateFQNs) {
        this.registry = registry;
        this.allowDuplicateFQNs = allowDuplicateFQNs;
        this.fqnToEntryMap = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
        this.listeners = new HashSet<>();
    }

    @Override
    public IQuerySpecificationRegistry getRegistry() {
        return registry;
    }

    @Override
    public Iterable<IQuerySpecificationRegistryEntry> getEntries() {
        return fqnToEntryMap.distinctValuesStream().collect(Collectors.toSet());
    }

    @Override
    public Set<String> getQuerySpecificationFQNs() {
        return fqnToEntryMap.distinctKeysStream().collect(Collectors.toSet());
    }

    @Override
    public boolean hasQuerySpecificationFQN(String fullyQualifiedName) {
        Preconditions.checkArgument(fullyQualifiedName != null, "FQN must not be null!");
        return fqnToEntryMap.lookupExists(fullyQualifiedName);
    }

    @Override
    public Set<IQuerySpecificationRegistryEntry> getEntries(String fullyQualifiedName) {
        Preconditions.checkArgument(fullyQualifiedName != null, "FQN must not be null!");
        IMemoryView<IQuerySpecificationRegistryEntry> entries = fqnToEntryMap.lookupOrEmpty(fullyQualifiedName);
        return Collections.unmodifiableSet(entries.distinctValues());
    }

    @Override
    public void addViewListener(IQuerySpecificationRegistryChangeListener listener) {
        Preconditions.checkArgument(listener != null, "Null listener not supported");
        listeners.add(listener);
    }

    @Override
    public void removeViewListener(IQuerySpecificationRegistryChangeListener listener) {
        Preconditions.checkArgument(listener != null, "Null listener not supported");
        listeners.remove(listener);
    }

    @Override
    public void entryAdded(IQuerySpecificationRegistryEntry entry) {
        if (isEntryRelevant(entry)) {
            String fullyQualifiedName = entry.getFullyQualifiedName();
            IMemoryView<IQuerySpecificationRegistryEntry> duplicates = fqnToEntryMap.lookup(fullyQualifiedName);
            if(!allowDuplicateFQNs && duplicates != null) {
                Set<IQuerySpecificationRegistryEntry> removed = new HashSet<>(duplicates.distinctValues());
                for (IQuerySpecificationRegistryEntry e : removed) {
                    fqnToEntryMap.removePair(fullyQualifiedName, e);
                    notifyListeners(e, false);
                }
            }
            fqnToEntryMap.addPair(fullyQualifiedName, entry);
            notifyListeners(entry, true);
        }
    }

    @Override
    public void entryRemoved(IQuerySpecificationRegistryEntry entry) {
        if (isEntryRelevant(entry)) {
            String fullyQualifiedName = entry.getFullyQualifiedName();
            fqnToEntryMap.removePair(fullyQualifiedName, entry);
            notifyListeners(entry, false);
        }
    }

    private void notifyListeners(IQuerySpecificationRegistryEntry entry, boolean addition) {
        for (IQuerySpecificationRegistryChangeListener listener : listeners) {
            try {
                if(addition){
                    listener.entryAdded(entry);
                } else {
                    listener.entryRemoved(entry);
                }
            } catch (Exception ex) {
                Logger logger = ViatraQueryLoggingUtil.getLogger(AbstractRegistryView.class);
                String formatString = addition ? LISTENER_EXCEPTION_ADD : LISTENER_EXCEPTION_REMOVE;
                logger.error(String.format(formatString, listener), ex);
            }
        }
    }

}