aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/AbstractIndexTable.java
blob: 7b557c3396a2405011a2677beacc6557ad4b51b8 (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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/*******************************************************************************
 * Copyright (c) 2010-2018, Gabor Bergmann, 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.matchers.scopes.tables;

import java.util.List;

import tools.refinery.viatra.runtime.matchers.context.IInputKey;
import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener;
import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;

/**
 * <p>
 * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
 * part of a work in progress. There is no guarantee that this API will
 * work or that it will remain the same.
 *
 * @since 2.0
 * @author Gabor Bergmann
 */
public abstract class AbstractIndexTable implements IIndexTable {

    private IInputKey inputKey;
    protected ITableContext tableContext;
    
    protected final TupleMask emptyMask;
    protected final Tuple emptyTuple;


    public AbstractIndexTable(IInputKey inputKey, ITableContext tableContext) {
        this.inputKey = inputKey;
        this.tableContext = tableContext;
        
        this.emptyMask = TupleMask.empty(getInputKey().getArity());
        this.emptyTuple = Tuples.flatTupleOf(new Object[inputKey.getArity()]);
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + ":" + inputKey.getPrettyPrintableName();
    }

    @Override
    public IInputKey getInputKey() {
        return inputKey;
    }

    protected void logError(String message) {
        tableContext.logError(message);
    }
    
    
    /// UPDATE HANDLING SECTION
    
    // The entire mechanism is designed to accommodate a large number of update listeners,
    // but maybe there will typically be only a single, universal (unseeded) listener? 
    // TODO Create special handling for that case. 
    
    // Invariant: true iff #listenerGroupsAndSeed is nonempty
    protected boolean emitNotifications = false; 
    // Subscribed listeners grouped by their seed mask (e.g. all those that seed columns 2 and 5 are together), 
    // groups are stored in a list for quick delivery-time iteration (at the expense of adding / removing);
    // individual listeners can be looked up based on their seed tuple 
    protected List<IListenersWithSameMask> listenerGroups = CollectionsFactory.createObserverList();

    
    /**
     * Implementors shall call this to deliver all notifications.
     * Call may be conditioned to {@link #emitNotifications}
     */
    protected void deliverChangeNotifications(Tuple updateTuple, boolean isInsertion) {
        for (IListenersWithSameMask listenersForSeed : listenerGroups) {
            listenersForSeed.deliver(updateTuple, isInsertion);
        }
    }
    
    @Override
    public void addUpdateListener(Tuple seed, IQueryRuntimeContextListener listener) {
        TupleMask seedMask;
        if (seed == null) {
            seed = emptyTuple;
            seedMask = emptyMask;
        } else {
            seedMask = TupleMask.fromNonNullIndices(seed);
        }
        IListenersWithSameMask listenerGroup = getListenerGroup(seedMask);
        if (listenerGroup == null) { // create new group
            switch (seedMask.getSize()) {
            case 0:
                listenerGroup = new UniversalListeners();
                break;
            case 1: 
                listenerGroup = new ColumnBoundListeners(seedMask.indices[0]);
                break;
            default:
                listenerGroup = new GenericBoundListeners(seedMask);                        
            }
            listenerGroups.add(listenerGroup);
            emitNotifications = true;
        }
        listenerGroup.addUpdateListener(seed, listener);
    }

    @Override
    public void removeUpdateListener(Tuple seed, IQueryRuntimeContextListener listener) {
        TupleMask seedMask;
        if (seed == null) {
            seed = emptyTuple;
            seedMask = emptyMask;
        } else {
            seedMask = TupleMask.fromNonNullIndices(seed);
        }
        IListenersWithSameMask listenerGroup = getListenerGroup(seedMask);
        if (listenerGroup == null) 
            throw new IllegalStateException("no listener subscribed with mask" + seedMask);
        
        if (listenerGroup.removeUpdateListener(seed, listener)) {
            listenerGroups.remove(listenerGroup);
            emitNotifications = !listenerGroups.isEmpty();
        }
    }
    
    protected IListenersWithSameMask getListenerGroup(TupleMask seedMask) {
        for (IListenersWithSameMask candidateGroup : listenerGroups) { // group already exists?
            if (seedMask.equals(candidateGroup.getSeedMask())) {
                return candidateGroup;
            }
        }
        return null;
    }
    
    
    /**
     * Represents all listeners subscribed to seeds with the given seed mask.
     * 
     * @author Bergmann Gabor
     */
    protected static interface IListenersWithSameMask { 
        
        public TupleMask getSeedMask();
        
        public void deliver(Tuple updateTuple, boolean isInsertion);
        
        public void addUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener);
        /**
         * @return true if this was the last listener, and the {@link IListenersWithSameMask} can be disposed of.
         */
        public boolean removeUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener);
    }
    /** 
     * Listeners interested in all tuples
     */
    protected final class UniversalListeners implements IListenersWithSameMask {
        private final TupleMask mask = TupleMask.empty(inputKey.getArity());
        private List<IQueryRuntimeContextListener> listeners = CollectionsFactory.createObserverList();
        
        @Override
        public TupleMask getSeedMask() {
            return mask;
        }
        @Override
        public void deliver(Tuple updateTuple, boolean isInsertion) {
            IInputKey key = inputKey;
            for (IQueryRuntimeContextListener listener : listeners) {
                listener.update(key, updateTuple, isInsertion);
            }
        }
        @Override
        public void addUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) {
            listeners.add(listener);
        }
        @Override
        public boolean removeUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) {
            listeners.remove(listener);
            return listeners.isEmpty();
        }
    }
    /** 
     * Listeners interested in all tuples seeded by a single columns
     */
    protected final class ColumnBoundListeners implements IListenersWithSameMask {
        private int seedPosition;
        protected final TupleMask mask;
        // indexed by projected seed tuple
        protected IMultiLookup<Object,IQueryRuntimeContextListener> listeners = 
                CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
        
        public ColumnBoundListeners(int seedPosition) {
            this.seedPosition = seedPosition;
            this.mask = TupleMask.selectSingle(seedPosition, inputKey.getArity());
        }
        
        @Override
        public TupleMask getSeedMask() {
            return mask;
        }
        @Override
        public void deliver(Tuple updateTuple, boolean isInsertion) {
            IInputKey key = inputKey;
            Object projectedSeed = updateTuple.get(seedPosition);
           for (IQueryRuntimeContextListener listener : listeners.lookupOrEmpty(projectedSeed)) {
                listener.update(key, updateTuple, isInsertion);
            }
        }
        @Override
        public void addUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) {
            Object projectedSeed = originalSeed.get(seedPosition);
            listeners.addPair(projectedSeed, listener);
        }
        @Override
        public boolean removeUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) {
            Object projectedSeed = originalSeed.get(seedPosition);
            listeners.removePair(projectedSeed, listener);
            return listeners.countKeys() == 0;
        }
    }
   /** 
     * Listeners interested in all tuples seeded by a tuple of values
     */
    protected final class GenericBoundListeners implements IListenersWithSameMask {
        protected final TupleMask mask;
        // indexed by projected seed tuple
        protected IMultiLookup<Tuple,IQueryRuntimeContextListener> listeners = 
                CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
        
        public GenericBoundListeners(TupleMask mask) {
            this.mask = mask;
        }
        
        @Override
        public TupleMask getSeedMask() {
            return mask;
        }
        @Override
        public void deliver(Tuple updateTuple, boolean isInsertion) {
            IInputKey key = inputKey;
            Tuple projectedSeed = mask.transform(updateTuple);
            for (IQueryRuntimeContextListener listener : listeners.lookupOrEmpty(projectedSeed)) {
                listener.update(key, updateTuple, isInsertion);
            }
        }
        @Override
        public void addUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) {
            Tuple projectedSeed = mask.transform(originalSeed);
            listeners.addPair(projectedSeed, listener);
        }
        @Override
        public boolean removeUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) {
            Tuple projectedSeed = mask.transform(originalSeed);
            listeners.removePair(projectedSeed, listener);
            return listeners.countKeys() == 0;
        }
    }


}