diff options
author | 2023-01-23 20:27:55 +0100 | |
---|---|---|
committer | 2023-01-29 00:02:28 +0100 | |
commit | d91acf3690682d243dbc150df902525b6e545c2f (patch) | |
tree | f39695cc193828df0b78030b5a56bd968e277457 /subprojects/store-query-viatra/src/main | |
parent | chore(deps): bump dependencies (diff) | |
download | refinery-d91acf3690682d243dbc150df902525b6e545c2f.tar.gz refinery-d91acf3690682d243dbc150df902525b6e545c2f.tar.zst refinery-d91acf3690682d243dbc150df902525b6e545c2f.zip |
refactor: Model store and query API
Use Adapters to simplify API usage.
Diffstat (limited to 'subprojects/store-query-viatra/src/main')
26 files changed, 638 insertions, 674 deletions
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java new file mode 100644 index 00000000..ecac570b --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java | |||
@@ -0,0 +1,22 @@ | |||
1 | package tools.refinery.store.query.viatra; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapterBuilderFactory; | ||
4 | import tools.refinery.store.model.ModelStoreBuilder; | ||
5 | import tools.refinery.store.query.ModelQuery; | ||
6 | import tools.refinery.store.query.ModelQueryAdapter; | ||
7 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryBuilderImpl; | ||
8 | |||
9 | public final class ViatraModelQuery extends ModelAdapterBuilderFactory<ModelQueryAdapter, ViatraModelQueryStoreAdapter, | ||
10 | ViatraModelQueryBuilder> { | ||
11 | public static final ViatraModelQuery ADAPTER = new ViatraModelQuery(); | ||
12 | |||
13 | private ViatraModelQuery() { | ||
14 | super(ModelQueryAdapter.class, ViatraModelQueryStoreAdapter.class, ViatraModelQueryBuilder.class); | ||
15 | extendsAdapter(ModelQuery.ADAPTER); | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public ViatraModelQueryBuilder createBuilder(ModelStoreBuilder storeBuilder) { | ||
20 | return new ViatraModelQueryBuilderImpl(storeBuilder); | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java new file mode 100644 index 00000000..ee445a79 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java | |||
@@ -0,0 +1,48 @@ | |||
1 | package tools.refinery.store.query.viatra; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
6 | import tools.refinery.store.model.ModelStore; | ||
7 | import tools.refinery.store.query.DNF; | ||
8 | import tools.refinery.store.query.ModelQueryBuilder; | ||
9 | |||
10 | import java.util.Collection; | ||
11 | import java.util.function.Function; | ||
12 | |||
13 | @SuppressWarnings("UnusedReturnValue") | ||
14 | public interface ViatraModelQueryBuilder extends ModelQueryBuilder { | ||
15 | ViatraModelQueryBuilder engineOptions(ViatraQueryEngineOptions engineOptions); | ||
16 | |||
17 | ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint); | ||
18 | |||
19 | ViatraModelQueryBuilder backend(IQueryBackendFactory queryBackendFactory); | ||
20 | |||
21 | ViatraModelQueryBuilder cachingBackend(IQueryBackendFactory queryBackendFactory); | ||
22 | |||
23 | ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory); | ||
24 | |||
25 | @Override | ||
26 | default ViatraModelQueryBuilder queries(DNF... queries) { | ||
27 | ModelQueryBuilder.super.queries(queries); | ||
28 | return this; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | default ViatraModelQueryBuilder queries(Collection<? extends DNF> queries) { | ||
33 | ModelQueryBuilder.super.queries(queries); | ||
34 | return this; | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | ViatraModelQueryBuilder query(DNF query); | ||
39 | |||
40 | ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint); | ||
41 | |||
42 | ViatraModelQueryBuilder computeHint(Function<DNF, QueryEvaluationHint> computeHint); | ||
43 | |||
44 | ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint); | ||
45 | |||
46 | @Override | ||
47 | ViatraModelQueryStoreAdapter createStoreAdapter(ModelStore store); | ||
48 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java new file mode 100644 index 00000000..d52575d2 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java | |||
@@ -0,0 +1,8 @@ | |||
1 | package tools.refinery.store.query.viatra; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; | ||
4 | import tools.refinery.store.query.ModelQueryStoreAdapter; | ||
5 | |||
6 | public interface ViatraModelQueryStoreAdapter extends ModelQueryStoreAdapter { | ||
7 | ViatraQueryEngineOptions getEngineOptions(); | ||
8 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java deleted file mode 100644 index 94d2db4f..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraQueryableModelStore.java +++ /dev/null | |||
@@ -1,140 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
4 | import tools.refinery.store.model.ModelDiffCursor; | ||
5 | import tools.refinery.store.model.ModelStore; | ||
6 | import tools.refinery.store.model.ModelStoreImpl; | ||
7 | import tools.refinery.store.model.RelationLike; | ||
8 | import tools.refinery.store.model.representation.AnyDataRepresentation; | ||
9 | import tools.refinery.store.model.representation.DataRepresentation; | ||
10 | import tools.refinery.store.query.DNF; | ||
11 | import tools.refinery.store.query.DNFAnd; | ||
12 | import tools.refinery.store.query.QueryableModel; | ||
13 | import tools.refinery.store.query.QueryableModelStore; | ||
14 | import tools.refinery.store.query.atom.*; | ||
15 | import tools.refinery.store.query.viatra.internal.RawPatternMatcher; | ||
16 | import tools.refinery.store.query.viatra.internal.ViatraQueryableModel; | ||
17 | import tools.refinery.store.query.viatra.internal.pquery.DNF2PQuery; | ||
18 | import tools.refinery.store.query.view.AnyRelationView; | ||
19 | |||
20 | import java.util.Collections; | ||
21 | import java.util.HashMap; | ||
22 | import java.util.Map; | ||
23 | import java.util.Set; | ||
24 | |||
25 | public class ViatraQueryableModelStore implements QueryableModelStore { | ||
26 | protected final ModelStore store; | ||
27 | |||
28 | protected final Set<AnyRelationView> relationViews; | ||
29 | |||
30 | protected final Map<DNF, GenericQuerySpecification<RawPatternMatcher>> predicates; | ||
31 | |||
32 | public ViatraQueryableModelStore(ModelStore store, Set<AnyRelationView> relationViews, | ||
33 | Set<DNF> predicates) { | ||
34 | this.store = store; | ||
35 | validateViews(store.getDataRepresentations(), relationViews); | ||
36 | this.relationViews = Collections.unmodifiableSet(relationViews); | ||
37 | validatePredicates(relationViews, predicates); | ||
38 | this.predicates = initPredicates(predicates); | ||
39 | } | ||
40 | |||
41 | public ViatraQueryableModelStore(Set<AnyDataRepresentation> dataRepresentations, | ||
42 | Set<AnyRelationView> relationViews, Set<DNF> predicates) { | ||
43 | this(new ModelStoreImpl(dataRepresentations), relationViews, predicates); | ||
44 | } | ||
45 | |||
46 | private void validateViews(Set<AnyDataRepresentation> dataRepresentations, Set<AnyRelationView> relationViews) { | ||
47 | for (var relationView : relationViews) { | ||
48 | if (!dataRepresentations.contains(relationView.getRepresentation())) { | ||
49 | throw new IllegalArgumentException("%s %s added to %s without a referred representation.".formatted( | ||
50 | DataRepresentation.class.getSimpleName(), relationView.getName(), | ||
51 | QueryableModelStore.class.getSimpleName())); | ||
52 | } | ||
53 | } | ||
54 | } | ||
55 | |||
56 | private void validatePredicates(Set<AnyRelationView> relationViews, Set<DNF> predicates) { | ||
57 | for (DNF dnfPredicate : predicates) { | ||
58 | for (DNFAnd clause : dnfPredicate.getClauses()) { | ||
59 | for (DNFAtom atom : clause.constraints()) { | ||
60 | if (atom instanceof RelationViewAtom relationViewAtom) { | ||
61 | validateRelationAtom(relationViews, dnfPredicate, relationViewAtom); | ||
62 | } else if (atom instanceof CallAtom<?> queryCallAtom) { | ||
63 | validatePredicateAtom(predicates, dnfPredicate, queryCallAtom); | ||
64 | } else if (!(atom instanceof EquivalenceAtom || atom instanceof ConstantAtom)) { | ||
65 | throw new IllegalArgumentException("Unknown constraint: " + atom.toString()); | ||
66 | } | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | |||
72 | private void validateRelationAtom(Set<AnyRelationView> relationViews, DNF dnfPredicate, | ||
73 | RelationViewAtom relationViewAtom) { | ||
74 | if (!relationViews.contains(relationViewAtom.getTarget())) { | ||
75 | throw new IllegalArgumentException( | ||
76 | "%s %s contains reference to a view %s that is not in the model.".formatted( | ||
77 | DNF.class.getSimpleName(), dnfPredicate.getUniqueName(), | ||
78 | relationViewAtom.getTarget().getName())); | ||
79 | } | ||
80 | } | ||
81 | |||
82 | private void validatePredicateReference(Set<DNF> predicates, DNF dnfPredicate, RelationLike target) { | ||
83 | if (!(target instanceof DNF dnfTarget) || !predicates.contains(dnfTarget)) { | ||
84 | throw new IllegalArgumentException( | ||
85 | "%s %s contains reference to a predicate %s that is not in the model.".formatted( | ||
86 | DNF.class.getSimpleName(), dnfPredicate.getUniqueName(), target.getName())); | ||
87 | } | ||
88 | } | ||
89 | |||
90 | private void validatePredicateAtom(Set<DNF> predicates, DNF dnfPredicate, CallAtom<?> queryCallAtom) { | ||
91 | validatePredicateReference(predicates, dnfPredicate, queryCallAtom.getTarget()); | ||
92 | } | ||
93 | |||
94 | |||
95 | private Map<DNF, GenericQuerySpecification<RawPatternMatcher>> initPredicates(Set<DNF> predicates) { | ||
96 | Map<DNF, GenericQuerySpecification<RawPatternMatcher>> result = new HashMap<>(); | ||
97 | var dnf2PQuery = new DNF2PQuery(); | ||
98 | for (DNF dnfPredicate : predicates) { | ||
99 | GenericQuerySpecification<RawPatternMatcher> query = dnf2PQuery.translate(dnfPredicate).build(); | ||
100 | result.put(dnfPredicate, query); | ||
101 | } | ||
102 | |||
103 | return result; | ||
104 | } | ||
105 | |||
106 | @Override | ||
107 | public Set<AnyDataRepresentation> getDataRepresentations() { | ||
108 | return store.getDataRepresentations(); | ||
109 | } | ||
110 | |||
111 | @Override | ||
112 | public Set<AnyRelationView> getViews() { | ||
113 | return this.relationViews; | ||
114 | } | ||
115 | |||
116 | @Override | ||
117 | public Set<DNF> getPredicates() { | ||
118 | return predicates.keySet(); | ||
119 | } | ||
120 | |||
121 | @Override | ||
122 | public QueryableModel createModel() { | ||
123 | return new ViatraQueryableModel(this, this.store.createModel(), predicates); | ||
124 | } | ||
125 | |||
126 | @Override | ||
127 | public QueryableModel createModel(long state) { | ||
128 | return new ViatraQueryableModel(this, this.store.createModel(state), predicates); | ||
129 | } | ||
130 | |||
131 | @Override | ||
132 | public synchronized Set<Long> getStates() { | ||
133 | return this.store.getStates(); | ||
134 | } | ||
135 | |||
136 | @Override | ||
137 | public synchronized ModelDiffCursor getDiffCursor(long from, long to) { | ||
138 | return this.store.getDiffCursor(from, to); | ||
139 | } | ||
140 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java index 133c4c72..21dcaf15 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java | |||
@@ -6,34 +6,20 @@ import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; | |||
6 | import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; | 6 | import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; |
7 | import org.eclipse.viatra.query.runtime.api.scope.QueryScope; | 7 | import org.eclipse.viatra.query.runtime.api.scope.QueryScope; |
8 | import tools.refinery.store.model.Model; | 8 | import tools.refinery.store.model.Model; |
9 | import tools.refinery.store.model.representation.Relation; | ||
10 | import tools.refinery.store.query.viatra.internal.context.RelationalEngineContext; | 9 | import tools.refinery.store.query.viatra.internal.context.RelationalEngineContext; |
11 | import tools.refinery.store.query.viatra.internal.viewupdate.ModelUpdateListener; | 10 | import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; |
12 | import tools.refinery.store.query.view.AnyRelationView; | 11 | import tools.refinery.store.query.view.AnyRelationView; |
13 | import tools.refinery.store.tuple.Tuple; | ||
14 | 12 | ||
15 | import java.util.Set; | 13 | import java.util.Collection; |
16 | 14 | ||
17 | public class RelationalScope extends QueryScope { | 15 | public class RelationalScope extends QueryScope { |
18 | private final Model model; | 16 | private final Model model; |
19 | 17 | ||
20 | private final ModelUpdateListener updateListener; | 18 | private final ModelUpdateListener updateListener; |
21 | 19 | ||
22 | public RelationalScope(Model model, Set<AnyRelationView> relationViews) { | 20 | public RelationalScope(Model model, Collection<AnyRelationView> relationViews) { |
23 | this.model = model; | 21 | this.model = model; |
24 | this.updateListener = new ModelUpdateListener(relationViews); | 22 | updateListener = new ModelUpdateListener(model, relationViews); |
25 | } | ||
26 | |||
27 | public <D> void processUpdate(Relation<D> relation, Tuple key, D oldValue, D newValue) { | ||
28 | updateListener.addUpdate(relation, key, oldValue, newValue); | ||
29 | } | ||
30 | |||
31 | public boolean hasChanges() { | ||
32 | return updateListener.hasChanges(); | ||
33 | } | ||
34 | |||
35 | public void flush() { | ||
36 | updateListener.flush(); | ||
37 | } | 23 | } |
38 | 24 | ||
39 | @Override | 25 | @Override |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java new file mode 100644 index 00000000..3c276935 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java | |||
@@ -0,0 +1,117 @@ | |||
1 | package tools.refinery.store.query.viatra.internal; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; | ||
4 | import org.eclipse.viatra.query.runtime.api.GenericQueryGroup; | ||
5 | import org.eclipse.viatra.query.runtime.api.IQuerySpecification; | ||
6 | import org.eclipse.viatra.query.runtime.internal.apiimpl.ViatraQueryEngineImpl; | ||
7 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; | ||
8 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.query.DNF; | ||
11 | import tools.refinery.store.query.ModelQueryAdapter; | ||
12 | import tools.refinery.store.query.ModelQueryStoreAdapter; | ||
13 | import tools.refinery.store.query.ResultSet; | ||
14 | |||
15 | import java.lang.invoke.MethodHandle; | ||
16 | import java.lang.invoke.MethodHandles; | ||
17 | import java.util.*; | ||
18 | |||
19 | public class ViatraModelQueryAdapterImpl implements ModelQueryAdapter { | ||
20 | private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; | ||
21 | private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; | ||
22 | |||
23 | private final Model model; | ||
24 | private final ViatraModelQueryStoreAdapterImpl storeAdapter; | ||
25 | private final ViatraQueryEngineImpl queryEngine; | ||
26 | private final MethodHandle setUpdatePropagationDelayedHandle; | ||
27 | private final MethodHandle getQueryBackendsHandle; | ||
28 | private final Map<DNF, ResultSet> resultSets; | ||
29 | |||
30 | ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { | ||
31 | this.model = model; | ||
32 | this.storeAdapter = storeAdapter; | ||
33 | var scope = new RelationalScope(model, storeAdapter.getRelationViews()); | ||
34 | queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope); | ||
35 | |||
36 | try { | ||
37 | var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup()); | ||
38 | setUpdatePropagationDelayedHandle = lookup.findSetter(ViatraQueryEngineImpl.class, | ||
39 | DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE); | ||
40 | getQueryBackendsHandle = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME, | ||
41 | Map.class); | ||
42 | } catch (IllegalAccessException | NoSuchFieldException e) { | ||
43 | throw new IllegalStateException("Cannot access private members of %s" | ||
44 | .formatted(ViatraQueryEngineImpl.class.getName()), e); | ||
45 | } | ||
46 | |||
47 | var querySpecifications = storeAdapter.getQuerySpecifications(); | ||
48 | GenericQueryGroup.of( | ||
49 | Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream() | ||
50 | ).prepare(queryEngine); | ||
51 | resultSets = new HashMap<>(querySpecifications.size()); | ||
52 | for (var entry : querySpecifications.entrySet()) { | ||
53 | var matcher = queryEngine.getMatcher(entry.getValue()); | ||
54 | resultSets.put(entry.getKey(), matcher); | ||
55 | } | ||
56 | |||
57 | setUpdatePropagationDelayed(true); | ||
58 | } | ||
59 | |||
60 | private void setUpdatePropagationDelayed(boolean value) { | ||
61 | try { | ||
62 | setUpdatePropagationDelayedHandle.invokeExact(queryEngine, value); | ||
63 | } catch (Error e) { | ||
64 | // Fatal JVM errors should not be wrapped. | ||
65 | throw e; | ||
66 | } catch (Throwable e) { | ||
67 | throw new IllegalStateException("Cannot set %s".formatted(DELAY_MESSAGE_DELIVERY_FIELD_NAME), e); | ||
68 | } | ||
69 | } | ||
70 | |||
71 | private Collection<IQueryBackend> getQueryBackends() { | ||
72 | try { | ||
73 | @SuppressWarnings("unchecked") | ||
74 | var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) getQueryBackendsHandle.invokeExact(queryEngine); | ||
75 | return backendMap.values(); | ||
76 | } catch (Error e) { | ||
77 | // Fatal JVM errors should not be wrapped. | ||
78 | throw e; | ||
79 | } catch (Throwable e) { | ||
80 | throw new IllegalStateException("Cannot get %s".formatted(QUERY_BACKENDS_FIELD_NAME), e); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | @Override | ||
85 | public Model getModel() { | ||
86 | return model; | ||
87 | } | ||
88 | |||
89 | @Override | ||
90 | public ModelQueryStoreAdapter getStoreAdapter() { | ||
91 | return storeAdapter; | ||
92 | } | ||
93 | |||
94 | @Override | ||
95 | public ResultSet getResultSet(DNF query) { | ||
96 | var resultSet = resultSets.get(query); | ||
97 | if (resultSet == null) { | ||
98 | throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name())); | ||
99 | } | ||
100 | return resultSet; | ||
101 | } | ||
102 | |||
103 | @Override | ||
104 | public void flushChanges() { | ||
105 | if (!queryEngine.isUpdatePropagationDelayed()) { | ||
106 | throw new IllegalStateException("Trying to flush changes while changes are already being flushed"); | ||
107 | } | ||
108 | setUpdatePropagationDelayed(false); | ||
109 | try { | ||
110 | for (var queryBackend : getQueryBackends()) { | ||
111 | queryBackend.flushUpdates(); | ||
112 | } | ||
113 | } finally { | ||
114 | setUpdatePropagationDelayed(true); | ||
115 | } | ||
116 | } | ||
117 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java new file mode 100644 index 00000000..5105c9a7 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java | |||
@@ -0,0 +1,116 @@ | |||
1 | package tools.refinery.store.query.viatra.internal; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.IQuerySpecification; | ||
4 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; | ||
5 | import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchGenericBackendFactory; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; | ||
7 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
8 | import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory; | ||
9 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.store.model.ModelStoreBuilder; | ||
12 | import tools.refinery.store.query.DNF; | ||
13 | import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; | ||
14 | import tools.refinery.store.query.viatra.internal.pquery.DNF2PQuery; | ||
15 | import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; | ||
16 | |||
17 | import java.util.Collections; | ||
18 | import java.util.LinkedHashMap; | ||
19 | import java.util.Map; | ||
20 | import java.util.function.Function; | ||
21 | |||
22 | public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder implements ViatraModelQueryBuilder { | ||
23 | private ViatraQueryEngineOptions.Builder engineOptionsBuilder; | ||
24 | private final DNF2PQuery dnf2PQuery = new DNF2PQuery(); | ||
25 | private final Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications = new LinkedHashMap<>(); | ||
26 | |||
27 | public ViatraModelQueryBuilderImpl(ModelStoreBuilder storeBuilder) { | ||
28 | super(storeBuilder); | ||
29 | engineOptionsBuilder = new ViatraQueryEngineOptions.Builder() | ||
30 | .withDefaultBackend(ReteBackendFactory.INSTANCE) | ||
31 | .withDefaultCachingBackend(ReteBackendFactory.INSTANCE) | ||
32 | .withDefaultSearchBackend(LocalSearchGenericBackendFactory.INSTANCE); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public ViatraModelQueryBuilder engineOptions(ViatraQueryEngineOptions engineOptions) { | ||
37 | engineOptionsBuilder = new ViatraQueryEngineOptions.Builder(engineOptions); | ||
38 | return this; | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) { | ||
43 | engineOptionsBuilder.withDefaultHint(queryEvaluationHint); | ||
44 | return this; | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | public ViatraModelQueryBuilder backend(IQueryBackendFactory queryBackendFactory) { | ||
49 | engineOptionsBuilder.withDefaultBackend(queryBackendFactory); | ||
50 | return this; | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public ViatraModelQueryBuilder cachingBackend(IQueryBackendFactory queryBackendFactory) { | ||
55 | engineOptionsBuilder.withDefaultCachingBackend(queryBackendFactory); | ||
56 | return this; | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | public ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory) { | ||
61 | engineOptionsBuilder.withDefaultSearchBackend(queryBackendFactory); | ||
62 | return this; | ||
63 | } | ||
64 | |||
65 | @Override | ||
66 | public ViatraModelQueryBuilder query(DNF query) { | ||
67 | if (querySpecifications.containsKey(query)) { | ||
68 | throw new IllegalArgumentException("%s was already added to the query engine".formatted(query.name())); | ||
69 | } | ||
70 | var pQuery = dnf2PQuery.translate(query); | ||
71 | querySpecifications.put(query, pQuery.build()); | ||
72 | return this; | ||
73 | } | ||
74 | |||
75 | @Override | ||
76 | public ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint) { | ||
77 | query(query); | ||
78 | hint(query, queryEvaluationHint); | ||
79 | return this; | ||
80 | } | ||
81 | |||
82 | @Override | ||
83 | public ViatraModelQueryBuilder computeHint(Function<DNF, QueryEvaluationHint> computeHint) { | ||
84 | dnf2PQuery.setComputeHint(computeHint); | ||
85 | return this; | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint) { | ||
90 | var pQuery = dnf2PQuery.getAlreadyTranslated(dnf); | ||
91 | if (pQuery == null) { | ||
92 | throw new IllegalArgumentException( | ||
93 | "Cannot specify hint for %s, because it was not added to the query engine".formatted(dnf.name())); | ||
94 | } | ||
95 | pQuery.setEvaluationHints(queryEvaluationHint); | ||
96 | return this; | ||
97 | } | ||
98 | |||
99 | @Override | ||
100 | public ViatraModelQueryStoreAdapterImpl createStoreAdapter(ModelStore store) { | ||
101 | validateSymbols(store); | ||
102 | return new ViatraModelQueryStoreAdapterImpl(store, engineOptionsBuilder.build(), dnf2PQuery.getRelationViews(), | ||
103 | Collections.unmodifiableMap(querySpecifications)); | ||
104 | } | ||
105 | |||
106 | private void validateSymbols(ModelStore store) { | ||
107 | var symbols = store.getSymbols(); | ||
108 | for (var relationView : dnf2PQuery.getRelationViews()) { | ||
109 | var symbol = relationView.getSymbol(); | ||
110 | if (!symbols.contains(symbol)) { | ||
111 | throw new IllegalArgumentException("Cannot query relation view %s: symbol %s is not in the model" | ||
112 | .formatted(relationView, symbol)); | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java new file mode 100644 index 00000000..d77b7f4b --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java | |||
@@ -0,0 +1,58 @@ | |||
1 | package tools.refinery.store.query.viatra.internal; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.IQuerySpecification; | ||
4 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; | ||
5 | import tools.refinery.store.model.Model; | ||
6 | import tools.refinery.store.model.ModelStore; | ||
7 | import tools.refinery.store.query.DNF; | ||
8 | import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter; | ||
9 | import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; | ||
10 | import tools.refinery.store.query.view.AnyRelationView; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | import java.util.Map; | ||
14 | |||
15 | public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAdapter { | ||
16 | private final ModelStore store; | ||
17 | private final ViatraQueryEngineOptions engineOptions; | ||
18 | private final Collection<AnyRelationView> relationViews; | ||
19 | private final Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications; | ||
20 | |||
21 | ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions, | ||
22 | Collection<AnyRelationView> relationViews, | ||
23 | Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications) { | ||
24 | this.store = store; | ||
25 | this.engineOptions = engineOptions; | ||
26 | this.relationViews = relationViews; | ||
27 | this.querySpecifications = querySpecifications; | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public ModelStore getStore() { | ||
32 | return store; | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public Collection<AnyRelationView> getRelationViews() { | ||
37 | return relationViews; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public Collection<DNF> getQueries() { | ||
42 | return querySpecifications.keySet(); | ||
43 | } | ||
44 | |||
45 | Map<DNF, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() { | ||
46 | return querySpecifications; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public ViatraQueryEngineOptions getEngineOptions() { | ||
51 | return engineOptions; | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public ViatraModelQueryAdapterImpl createModelAdapter(Model model) { | ||
56 | return new ViatraModelQueryAdapterImpl(model, this); | ||
57 | } | ||
58 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java deleted file mode 100644 index 5b06e266..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraQueryableModel.java +++ /dev/null | |||
@@ -1,222 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; | ||
4 | import org.eclipse.viatra.query.runtime.api.GenericQueryGroup; | ||
5 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
6 | import org.eclipse.viatra.query.runtime.api.IQueryGroup; | ||
7 | import tools.refinery.store.map.Cursor; | ||
8 | import tools.refinery.store.map.DiffCursor; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.model.ModelDiffCursor; | ||
11 | import tools.refinery.store.model.representation.AnyDataRepresentation; | ||
12 | import tools.refinery.store.model.representation.DataRepresentation; | ||
13 | import tools.refinery.store.model.representation.Relation; | ||
14 | import tools.refinery.store.query.QueryableModel; | ||
15 | import tools.refinery.store.query.QueryableModelStore; | ||
16 | import tools.refinery.store.query.DNF; | ||
17 | import tools.refinery.store.tuple.Tuple; | ||
18 | import tools.refinery.store.tuple.TupleLike; | ||
19 | |||
20 | import java.util.HashMap; | ||
21 | import java.util.Map; | ||
22 | import java.util.Optional; | ||
23 | import java.util.Set; | ||
24 | import java.util.stream.Stream; | ||
25 | |||
26 | public class ViatraQueryableModel implements QueryableModel { | ||
27 | protected final QueryableModelStore store; | ||
28 | |||
29 | protected final Model model; | ||
30 | |||
31 | protected final Map<DNF, GenericQuerySpecification<RawPatternMatcher>> predicates2PQuery; | ||
32 | |||
33 | protected RelationalScope scope; | ||
34 | |||
35 | protected AdvancedViatraQueryEngine engine; | ||
36 | |||
37 | protected Map<DNF, RawPatternMatcher> predicate2Matcher; | ||
38 | |||
39 | public ViatraQueryableModel(QueryableModelStore store, Model model, | ||
40 | Map<DNF, GenericQuerySpecification<RawPatternMatcher>> predicates2PQuery) { | ||
41 | this.store = store; | ||
42 | this.model = model; | ||
43 | this.predicates2PQuery = predicates2PQuery; | ||
44 | initEngine(); | ||
45 | } | ||
46 | |||
47 | private void initEngine() { | ||
48 | this.scope = new RelationalScope(this.model, this.store.getViews()); | ||
49 | this.engine = AdvancedViatraQueryEngine.createUnmanagedEngine(this.scope); | ||
50 | this.predicate2Matcher = initMatchers(this.engine, this.predicates2PQuery); | ||
51 | } | ||
52 | |||
53 | private Map<DNF, RawPatternMatcher> initMatchers( | ||
54 | AdvancedViatraQueryEngine engine, | ||
55 | Map<DNF, GenericQuerySpecification<RawPatternMatcher>> predicates2pQuery) { | ||
56 | // 1. prepare group | ||
57 | IQueryGroup queryGroup = GenericQueryGroup.of(Set.copyOf(predicates2pQuery.values())); | ||
58 | engine.prepareGroup(queryGroup, null); | ||
59 | |||
60 | // 2. then get all matchers | ||
61 | Map<DNF, RawPatternMatcher> result = new HashMap<>(); | ||
62 | for (var entry : predicates2pQuery.entrySet()) { | ||
63 | var matcher = engine.getMatcher(entry.getValue()); | ||
64 | result.put(entry.getKey(), matcher); | ||
65 | } | ||
66 | return result; | ||
67 | } | ||
68 | |||
69 | @Override | ||
70 | public Set<AnyDataRepresentation> getDataRepresentations() { | ||
71 | return model.getDataRepresentations(); | ||
72 | } | ||
73 | |||
74 | @Override | ||
75 | public Set<DNF> getPredicates() { | ||
76 | return store.getPredicates(); | ||
77 | } | ||
78 | |||
79 | @Override | ||
80 | public <K, V> V get(DataRepresentation<K, V> representation, K key) { | ||
81 | return model.get(representation, key); | ||
82 | } | ||
83 | |||
84 | @Override | ||
85 | public <K, V> Cursor<K, V> getAll(DataRepresentation<K, V> representation) { | ||
86 | return model.getAll(representation); | ||
87 | } | ||
88 | |||
89 | @SuppressWarnings("unchecked") | ||
90 | @Override | ||
91 | public <K, V> V put(DataRepresentation<K, V> representation, K key, V value) { | ||
92 | V oldValue = this.model.put(representation, key, value); | ||
93 | if (representation instanceof Relation<?> relation) { | ||
94 | this.scope.processUpdate((Relation<V>) relation, (Tuple) key, oldValue, value); | ||
95 | } | ||
96 | return oldValue; | ||
97 | } | ||
98 | |||
99 | @Override | ||
100 | public <K, V> void putAll(DataRepresentation<K, V> representation, Cursor<K, V> cursor) { | ||
101 | if (representation instanceof Relation<?>) { | ||
102 | //noinspection RedundantSuppression | ||
103 | @SuppressWarnings("unchecked") | ||
104 | Relation<V> relation = (Relation<V>) representation; | ||
105 | while (cursor.move()) { | ||
106 | Tuple key = (Tuple) cursor.getKey(); | ||
107 | V newValue = cursor.getValue(); | ||
108 | V oldValue = this.model.put(relation, key, newValue); | ||
109 | this.scope.processUpdate(relation, key, oldValue, newValue); | ||
110 | } | ||
111 | } else { | ||
112 | this.model.putAll(representation, cursor); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | @Override | ||
117 | public long getSize(AnyDataRepresentation representation) { | ||
118 | return model.getSize(representation); | ||
119 | } | ||
120 | |||
121 | protected RawPatternMatcher getMatcher(DNF predicate) { | ||
122 | var result = this.predicate2Matcher.get(predicate); | ||
123 | if (result == null) { | ||
124 | throw new IllegalArgumentException("Model does not contain predicate %s".formatted(predicate.getName())); | ||
125 | } else | ||
126 | return result; | ||
127 | } | ||
128 | |||
129 | protected void validateParameters(DNF predicate, Tuple parameters) { | ||
130 | int predicateArity = predicate.getParameters().size(); | ||
131 | int parameterArity = parameters.getSize(); | ||
132 | if (parameterArity != predicateArity) { | ||
133 | throw new IllegalArgumentException( | ||
134 | "Predicate %s with %d arity called with different number of parameters (%d)" | ||
135 | .formatted(predicate.getName(), predicateArity, parameterArity)); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | @Override | ||
140 | public boolean hasResult(DNF predicate) { | ||
141 | return getMatcher(predicate).hasResult(); | ||
142 | } | ||
143 | |||
144 | @Override | ||
145 | public boolean hasResult(DNF predicate, Tuple parameters) { | ||
146 | validateParameters(predicate, parameters); | ||
147 | return getMatcher(predicate).hasResult(parameters); | ||
148 | } | ||
149 | |||
150 | @Override | ||
151 | public Optional<TupleLike> oneResult(DNF predicate) { | ||
152 | return getMatcher(predicate).oneResult(); | ||
153 | } | ||
154 | |||
155 | @Override | ||
156 | public Optional<TupleLike> oneResult(DNF predicate, Tuple parameters) { | ||
157 | validateParameters(predicate, parameters); | ||
158 | return getMatcher(predicate).oneResult(parameters); | ||
159 | } | ||
160 | |||
161 | @Override | ||
162 | public Stream<TupleLike> allResults(DNF predicate) { | ||
163 | return getMatcher(predicate).allResults(); | ||
164 | } | ||
165 | |||
166 | @Override | ||
167 | public Stream<TupleLike> allResults(DNF predicate, Tuple parameters) { | ||
168 | validateParameters(predicate, parameters); | ||
169 | return getMatcher(predicate).allResults(parameters); | ||
170 | } | ||
171 | |||
172 | @Override | ||
173 | public int countResults(DNF predicate) { | ||
174 | return getMatcher(predicate).countResults(); | ||
175 | } | ||
176 | |||
177 | @Override | ||
178 | public int countResults(DNF predicate, Tuple parameters) { | ||
179 | validateParameters(predicate, parameters); | ||
180 | return getMatcher(predicate).countResults(parameters); | ||
181 | |||
182 | } | ||
183 | |||
184 | @Override | ||
185 | public boolean hasChanges() { | ||
186 | return scope.hasChanges(); | ||
187 | } | ||
188 | |||
189 | @Override | ||
190 | public void flushChanges() { | ||
191 | this.scope.flush(); | ||
192 | } | ||
193 | |||
194 | @Override | ||
195 | public ModelDiffCursor getDiffCursor(long to) { | ||
196 | return model.getDiffCursor(to); | ||
197 | } | ||
198 | |||
199 | @Override | ||
200 | public long commit() { | ||
201 | return this.model.commit(); | ||
202 | } | ||
203 | |||
204 | @Override | ||
205 | public void restore(long state) { | ||
206 | restoreWithDiffReplay(state); | ||
207 | } | ||
208 | |||
209 | private void restoreWithDiffReplay(long state) { | ||
210 | var modelDiffCursor = getDiffCursor(state); | ||
211 | for (AnyDataRepresentation anyDataRepresentation : this.getDataRepresentations()) { | ||
212 | var dataRepresentation = (DataRepresentation<?, ?>) anyDataRepresentation; | ||
213 | restoreRepresentationWithDiffReplay(modelDiffCursor, dataRepresentation); | ||
214 | } | ||
215 | } | ||
216 | |||
217 | private <K, V> void restoreRepresentationWithDiffReplay(ModelDiffCursor modelDiffCursor, | ||
218 | DataRepresentation<K, V> dataRepresentation) { | ||
219 | DiffCursor<K, V> diffCursor = modelDiffCursor.getCursor(dataRepresentation); | ||
220 | this.putAll(dataRepresentation, diffCursor); | ||
221 | } | ||
222 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java index ffd5f6de..e0bca9e0 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java | |||
@@ -2,10 +2,10 @@ package tools.refinery.store.query.viatra.internal.cardinality; | |||
2 | 2 | ||
3 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator; | 3 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator; |
4 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; | 4 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; |
5 | import tools.refinery.store.model.representation.cardinality.FiniteUpperCardinality; | 5 | import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; |
6 | import tools.refinery.store.model.representation.cardinality.UnboundedUpperCardinality; | 6 | import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality; |
7 | import tools.refinery.store.model.representation.cardinality.UpperCardinalities; | 7 | import tools.refinery.store.representation.cardinality.UpperCardinalities; |
8 | import tools.refinery.store.model.representation.cardinality.UpperCardinality; | 8 | import tools.refinery.store.representation.cardinality.UpperCardinality; |
9 | 9 | ||
10 | import java.util.stream.Stream; | 10 | import java.util.stream.Stream; |
11 | 11 | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java index d32d49ba..4eb8898b 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java | |||
@@ -5,7 +5,7 @@ import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; | |||
5 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; | 5 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; |
6 | 6 | ||
7 | import tools.refinery.store.model.Model; | 7 | import tools.refinery.store.model.Model; |
8 | import tools.refinery.store.query.viatra.internal.viewupdate.ModelUpdateListener; | 8 | import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; |
9 | 9 | ||
10 | public class RelationalEngineContext implements IEngineContext { | 10 | public class RelationalEngineContext implements IEngineContext { |
11 | private final IBaseIndex baseIndex = new DummyBaseIndexer(); | 11 | private final IBaseIndex baseIndex = new DummyBaseIndexer(); |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java index eb3c6fbd..47b83634 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java | |||
@@ -39,7 +39,7 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext { | |||
39 | 39 | ||
40 | public void ensureValidKey(IInputKey key) { | 40 | public void ensureValidKey(IInputKey key) { |
41 | if (!(key instanceof RelationViewWrapper)) { | 41 | if (!(key instanceof RelationViewWrapper)) { |
42 | throw new IllegalArgumentException("The input key %s is not a valid input key.".formatted(key)); | 42 | throw new IllegalArgumentException("The input key %s is not a valid input key".formatted(key)); |
43 | } | 43 | } |
44 | } | 44 | } |
45 | } | 45 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java index e01525e0..7375b240 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java | |||
@@ -8,7 +8,7 @@ import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | |||
8 | import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; | 8 | import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; |
9 | import tools.refinery.store.model.Model; | 9 | import tools.refinery.store.model.Model; |
10 | import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; | 10 | import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; |
11 | import tools.refinery.store.query.viatra.internal.viewupdate.ModelUpdateListener; | 11 | import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; |
12 | import tools.refinery.store.query.view.AnyRelationView; | 12 | import tools.refinery.store.query.view.AnyRelationView; |
13 | import tools.refinery.store.query.view.RelationView; | 13 | import tools.refinery.store.query.view.RelationView; |
14 | 14 | ||
@@ -48,12 +48,12 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
48 | 48 | ||
49 | @Override | 49 | @Override |
50 | public boolean isCoalescing() { | 50 | public boolean isCoalescing() { |
51 | return true; | 51 | return false; |
52 | } | 52 | } |
53 | 53 | ||
54 | @Override | 54 | @Override |
55 | public boolean isIndexed(IInputKey key, IndexingService service) { | 55 | public boolean isIndexed(IInputKey key, IndexingService service) { |
56 | if (key instanceof RelationView<?> relationalKey) { | 56 | if (key instanceof AnyRelationView relationalKey) { |
57 | return this.modelUpdateListener.containsRelationView(relationalKey); | 57 | return this.modelUpdateListener.containsRelationView(relationalKey); |
58 | } else { | 58 | } else { |
59 | return false; | 59 | return false; |
@@ -63,7 +63,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
63 | @Override | 63 | @Override |
64 | public void ensureIndexed(IInputKey key, IndexingService service) { | 64 | public void ensureIndexed(IInputKey key, IndexingService service) { |
65 | if (!isIndexed(key, service)) { | 65 | if (!isIndexed(key, service)) { |
66 | throw new IllegalStateException("Engine tries to index a new key " + key); | 66 | throw new IllegalStateException("Engine tries to index a new key %s".formatted(key)); |
67 | } | 67 | } |
68 | } | 68 | } |
69 | 69 | ||
@@ -73,7 +73,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
73 | if (modelUpdateListener.containsRelationView(relationViewKey)) { | 73 | if (modelUpdateListener.containsRelationView(relationViewKey)) { |
74 | return relationViewKey; | 74 | return relationViewKey; |
75 | } else { | 75 | } else { |
76 | throw new IllegalStateException("Query is asking for non-indexed key"); | 76 | throw new IllegalStateException("Query is asking for non-indexed key %s".formatted(relationViewKey)); |
77 | } | 77 | } |
78 | } else { | 78 | } else { |
79 | throw new IllegalStateException("Query is asking for non-relational key"); | 79 | throw new IllegalStateException("Query is asking for non-relational key"); |
@@ -131,7 +131,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
131 | 131 | ||
132 | @Override | 132 | @Override |
133 | public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { | 133 | public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { |
134 | var relationViewKey = (RelationView<?>) checkKey(key); | 134 | var relationViewKey = checkKey(key); |
135 | this.modelUpdateListener.addListener(key, relationViewKey, seed, listener); | 135 | this.modelUpdateListener.addListener(key, relationViewKey, seed, listener); |
136 | 136 | ||
137 | } | 137 | } |
@@ -168,7 +168,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
168 | } | 168 | } |
169 | 169 | ||
170 | @Override | 170 | @Override |
171 | public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException { | 171 | public void executeAfterTraversal(Runnable runnable) { |
172 | runnable.run(); | 172 | runnable.run(); |
173 | } | 173 | } |
174 | } | 174 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java index aa3fba6e..2b5618d2 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java | |||
@@ -1,5 +1,6 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.pquery; | 1 | package tools.refinery.store.query.viatra.internal.pquery; |
2 | 2 | ||
3 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
3 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; | 4 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; |
4 | import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; | 5 | import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; |
5 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality; | 6 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality; |
@@ -20,20 +21,28 @@ import tools.refinery.store.query.atom.*; | |||
20 | import tools.refinery.store.query.view.AnyRelationView; | 21 | import tools.refinery.store.query.view.AnyRelationView; |
21 | 22 | ||
22 | import java.util.*; | 23 | import java.util.*; |
24 | import java.util.function.Function; | ||
23 | import java.util.stream.Collectors; | 25 | import java.util.stream.Collectors; |
24 | 26 | ||
25 | public class DNF2PQuery { | 27 | public class DNF2PQuery { |
26 | private final Set<DNF> translating = new LinkedHashSet<>(); | 28 | private final Set<DNF> translating = new LinkedHashSet<>(); |
27 | 29 | ||
28 | private final Map<DNF, SimplePQuery> dnf2PQueryMap = new HashMap<>(); | 30 | private final Map<DNF, RawPQuery> dnf2PQueryMap = new HashMap<>(); |
29 | 31 | ||
30 | private final Map<AnyRelationView, RelationViewWrapper> view2WrapperMap = new HashMap<>(); | 32 | private final Map<AnyRelationView, RelationViewWrapper> view2WrapperMap = new LinkedHashMap<>(); |
31 | 33 | ||
32 | public SimplePQuery translate(DNF dnfQuery) { | 34 | private Function<DNF, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null, |
35 | QueryEvaluationHint.BackendRequirement.UNSPECIFIED); | ||
36 | |||
37 | public void setComputeHint(Function<DNF, QueryEvaluationHint> computeHint) { | ||
38 | this.computeHint = computeHint; | ||
39 | } | ||
40 | |||
41 | public RawPQuery translate(DNF dnfQuery) { | ||
33 | if (translating.contains(dnfQuery)) { | 42 | if (translating.contains(dnfQuery)) { |
34 | var path = translating.stream().map(DNF::getName).collect(Collectors.joining(" -> ")); | 43 | var path = translating.stream().map(DNF::name).collect(Collectors.joining(" -> ")); |
35 | throw new IllegalStateException("Circular reference %s -> %s detected".formatted(path, | 44 | throw new IllegalStateException("Circular reference %s -> %s detected".formatted(path, |
36 | dnfQuery.getName())); | 45 | dnfQuery.name())); |
37 | } | 46 | } |
38 | // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant | 47 | // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant |
39 | // way, which would cause a ConcurrentModificationException with computeIfAbsent. | 48 | // way, which would cause a ConcurrentModificationException with computeIfAbsent. |
@@ -50,8 +59,17 @@ public class DNF2PQuery { | |||
50 | return pQuery; | 59 | return pQuery; |
51 | } | 60 | } |
52 | 61 | ||
53 | private SimplePQuery doTranslate(DNF dnfQuery) { | 62 | public Collection<AnyRelationView> getRelationViews() { |
54 | var pQuery = new SimplePQuery(dnfQuery.getUniqueName()); | 63 | return Collections.unmodifiableCollection(view2WrapperMap.keySet()); |
64 | } | ||
65 | |||
66 | public RawPQuery getAlreadyTranslated(DNF dnfQuery) { | ||
67 | return dnf2PQueryMap.get(dnfQuery); | ||
68 | } | ||
69 | |||
70 | private RawPQuery doTranslate(DNF dnfQuery) { | ||
71 | var pQuery = new RawPQuery(dnfQuery.getUniqueName()); | ||
72 | pQuery.setEvaluationHints(computeHint.apply(dnfQuery)); | ||
55 | 73 | ||
56 | Map<Variable, PParameter> parameters = new HashMap<>(); | 74 | Map<Variable, PParameter> parameters = new HashMap<>(); |
57 | for (Variable variable : dnfQuery.getParameters()) { | 75 | for (Variable variable : dnfQuery.getParameters()) { |
@@ -86,7 +104,7 @@ public class DNF2PQuery { | |||
86 | translateEquivalenceAtom(equivalenceAtom, body); | 104 | translateEquivalenceAtom(equivalenceAtom, body); |
87 | } else if (constraint instanceof RelationViewAtom relationViewAtom) { | 105 | } else if (constraint instanceof RelationViewAtom relationViewAtom) { |
88 | translateRelationViewAtom(relationViewAtom, body); | 106 | translateRelationViewAtom(relationViewAtom, body); |
89 | } else if (constraint instanceof CallAtom<?> callAtom) { | 107 | } else if (constraint instanceof DNFCallAtom callAtom) { |
90 | translateCallAtom(callAtom, body); | 108 | translateCallAtom(callAtom, body); |
91 | } else if (constraint instanceof ConstantAtom constantAtom) { | 109 | } else if (constraint instanceof ConstantAtom constantAtom) { |
92 | translateConstantAtom(constantAtom, body); | 110 | translateConstantAtom(constantAtom, body); |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SimplePQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java index a367cbf2..5d0b9e82 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SimplePQuery.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java | |||
@@ -3,27 +3,24 @@ package tools.refinery.store.query.viatra.internal.pquery; | |||
3 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | 3 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; |
4 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; | 4 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; |
5 | import org.eclipse.viatra.query.runtime.api.scope.QueryScope; | 5 | import org.eclipse.viatra.query.runtime.api.scope.QueryScope; |
6 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
7 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; | 6 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; |
8 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; | 7 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; |
9 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; | 8 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; |
10 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; | 9 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; |
11 | import tools.refinery.store.query.viatra.internal.RawPatternMatcher; | ||
12 | import tools.refinery.store.query.viatra.internal.RelationalScope; | 10 | import tools.refinery.store.query.viatra.internal.RelationalScope; |
13 | 11 | ||
14 | import java.util.LinkedHashSet; | 12 | import java.util.LinkedHashSet; |
15 | import java.util.List; | 13 | import java.util.List; |
16 | import java.util.Set; | 14 | import java.util.Set; |
17 | 15 | ||
18 | public class SimplePQuery extends BasePQuery { | 16 | public class RawPQuery extends BasePQuery { |
19 | private final String fullyQualifiedName; | 17 | private final String fullyQualifiedName; |
20 | private List<PParameter> parameters; | 18 | private List<PParameter> parameters; |
21 | private final LinkedHashSet<PBody> bodies = new LinkedHashSet<>(); | 19 | private final LinkedHashSet<PBody> bodies = new LinkedHashSet<>(); |
22 | 20 | ||
23 | public SimplePQuery(String name) { | 21 | public RawPQuery(String name) { |
24 | super(PVisibility.PUBLIC); | 22 | super(PVisibility.PUBLIC); |
25 | fullyQualifiedName = name; | 23 | fullyQualifiedName = name; |
26 | setEvaluationHints(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.UNSPECIFIED)); | ||
27 | } | 24 | } |
28 | 25 | ||
29 | @Override | 26 | @Override |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java index 2c488319..e944e873 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RawPatternMatcher.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java | |||
@@ -1,7 +1,8 @@ | |||
1 | package tools.refinery.store.query.viatra.internal; | 1 | package tools.refinery.store.query.viatra.internal.pquery; |
2 | 2 | ||
3 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; | 3 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; |
4 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | 4 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; |
5 | import tools.refinery.store.query.ResultSet; | ||
5 | import tools.refinery.store.query.viatra.ViatraTupleLike; | 6 | import tools.refinery.store.query.viatra.ViatraTupleLike; |
6 | import tools.refinery.store.tuple.Tuple; | 7 | import tools.refinery.store.tuple.Tuple; |
7 | import tools.refinery.store.tuple.TupleLike; | 8 | import tools.refinery.store.tuple.TupleLike; |
@@ -9,7 +10,7 @@ import tools.refinery.store.tuple.TupleLike; | |||
9 | import java.util.Optional; | 10 | import java.util.Optional; |
10 | import java.util.stream.Stream; | 11 | import java.util.stream.Stream; |
11 | 12 | ||
12 | public class RawPatternMatcher extends GenericPatternMatcher { | 13 | public class RawPatternMatcher extends GenericPatternMatcher implements ResultSet { |
13 | protected final Object[] empty; | 14 | protected final Object[] empty; |
14 | 15 | ||
15 | public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) { | 16 | public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) { |
@@ -17,34 +18,42 @@ public class RawPatternMatcher extends GenericPatternMatcher { | |||
17 | empty = new Object[specification.getParameterNames().size()]; | 18 | empty = new Object[specification.getParameterNames().size()]; |
18 | } | 19 | } |
19 | 20 | ||
21 | @Override | ||
20 | public boolean hasResult() { | 22 | public boolean hasResult() { |
21 | return backend.hasMatch(empty); | 23 | return backend.hasMatch(empty); |
22 | } | 24 | } |
23 | 25 | ||
26 | @Override | ||
24 | public boolean hasResult(Tuple parameters) { | 27 | public boolean hasResult(Tuple parameters) { |
25 | return backend.hasMatch(toParametersArray(parameters)); | 28 | return backend.hasMatch(toParametersArray(parameters)); |
26 | } | 29 | } |
27 | 30 | ||
31 | @Override | ||
28 | public Optional<TupleLike> oneResult() { | 32 | public Optional<TupleLike> oneResult() { |
29 | return backend.getOneArbitraryMatch(empty).map(ViatraTupleLike::new); | 33 | return backend.getOneArbitraryMatch(empty).map(ViatraTupleLike::new); |
30 | } | 34 | } |
31 | 35 | ||
36 | @Override | ||
32 | public Optional<TupleLike> oneResult(Tuple parameters) { | 37 | public Optional<TupleLike> oneResult(Tuple parameters) { |
33 | return backend.getOneArbitraryMatch(toParametersArray(parameters)).map(ViatraTupleLike::new); | 38 | return backend.getOneArbitraryMatch(toParametersArray(parameters)).map(ViatraTupleLike::new); |
34 | } | 39 | } |
35 | 40 | ||
41 | @Override | ||
36 | public Stream<TupleLike> allResults() { | 42 | public Stream<TupleLike> allResults() { |
37 | return backend.getAllMatches(empty).map(ViatraTupleLike::new); | 43 | return backend.getAllMatches(empty).map(ViatraTupleLike::new); |
38 | } | 44 | } |
39 | 45 | ||
46 | @Override | ||
40 | public Stream<TupleLike> allResults(Tuple parameters) { | 47 | public Stream<TupleLike> allResults(Tuple parameters) { |
41 | return backend.getAllMatches(toParametersArray(parameters)).map(ViatraTupleLike::new); | 48 | return backend.getAllMatches(toParametersArray(parameters)).map(ViatraTupleLike::new); |
42 | } | 49 | } |
43 | 50 | ||
51 | @Override | ||
44 | public int countResults() { | 52 | public int countResults() { |
45 | return backend.countMatches(empty); | 53 | return backend.countMatches(empty); |
46 | } | 54 | } |
47 | 55 | ||
56 | @Override | ||
48 | public int countResults(Tuple parameters) { | 57 | public int countResults(Tuple parameters) { |
49 | return backend.countMatches(toParametersArray(parameters)); | 58 | return backend.countMatches(toParametersArray(parameters)); |
50 | } | 59 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java index e48648bf..c442add8 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java | |||
@@ -10,7 +10,7 @@ public class RelationViewWrapper extends BaseInputKeyWrapper<AnyRelationView> { | |||
10 | 10 | ||
11 | @Override | 11 | @Override |
12 | public String getPrettyPrintableName() { | 12 | public String getPrettyPrintableName() { |
13 | return wrappedKey.getName(); | 13 | return wrappedKey.name(); |
14 | } | 14 | } |
15 | 15 | ||
16 | @Override | 16 | @Override |
@@ -20,7 +20,7 @@ public class RelationViewWrapper extends BaseInputKeyWrapper<AnyRelationView> { | |||
20 | 20 | ||
21 | @Override | 21 | @Override |
22 | public int getArity() { | 22 | public int getArity() { |
23 | return wrappedKey.getArity(); | 23 | return wrappedKey.arity(); |
24 | } | 24 | } |
25 | 25 | ||
26 | @Override | 26 | @Override |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java new file mode 100644 index 00000000..1ae3daa7 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java | |||
@@ -0,0 +1,46 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.update; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
6 | import tools.refinery.store.model.Model; | ||
7 | import tools.refinery.store.query.view.AnyRelationView; | ||
8 | import tools.refinery.store.query.view.RelationView; | ||
9 | |||
10 | import java.util.Collection; | ||
11 | import java.util.HashMap; | ||
12 | import java.util.Map; | ||
13 | |||
14 | public class ModelUpdateListener { | ||
15 | private final Map<AnyRelationView, RelationViewUpdateListener<?>> relationViewUpdateListeners; | ||
16 | |||
17 | public ModelUpdateListener(Model model, Collection<AnyRelationView> relationViews) { | ||
18 | relationViewUpdateListeners = new HashMap<>(relationViews.size()); | ||
19 | for (var relationView : relationViews) { | ||
20 | registerView(model, (RelationView<?>) relationView); | ||
21 | } | ||
22 | } | ||
23 | |||
24 | private <T> void registerView(Model model, RelationView<T> relationView) { | ||
25 | var listener = RelationViewUpdateListener.of(relationView); | ||
26 | var interpretation = model.getInterpretation(relationView.getSymbol()); | ||
27 | interpretation.addListener(listener, true); | ||
28 | relationViewUpdateListeners.put(relationView, listener); | ||
29 | } | ||
30 | |||
31 | public boolean containsRelationView(AnyRelationView relationView) { | ||
32 | return relationViewUpdateListeners.containsKey(relationView); | ||
33 | } | ||
34 | |||
35 | public void addListener(IInputKey key, AnyRelationView relationView, ITuple seed, | ||
36 | IQueryRuntimeContextListener listener) { | ||
37 | var relationViewUpdateListener = relationViewUpdateListeners.get(relationView); | ||
38 | relationViewUpdateListener.addFilter(key, seed, listener); | ||
39 | } | ||
40 | |||
41 | public void removeListener(IInputKey key, AnyRelationView relationView, ITuple seed, | ||
42 | IQueryRuntimeContextListener listener) { | ||
43 | var relationViewUpdateListener = relationViewUpdateListeners.get(relationView); | ||
44 | relationViewUpdateListener.removeFilter(key, seed, listener); | ||
45 | } | ||
46 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java new file mode 100644 index 00000000..221f1b4a --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java | |||
@@ -0,0 +1,66 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.update; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
7 | |||
8 | import java.util.Arrays; | ||
9 | import java.util.Objects; | ||
10 | |||
11 | public final class RelationViewFilter { | ||
12 | private final IInputKey inputKey; | ||
13 | private final Object[] seed; | ||
14 | private final IQueryRuntimeContextListener listener; | ||
15 | |||
16 | public RelationViewFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { | ||
17 | this.inputKey = inputKey; | ||
18 | this.seed = seedToArray(seed); | ||
19 | this.listener = listener; | ||
20 | } | ||
21 | |||
22 | public void update(Tuple updateTuple, boolean isInsertion) { | ||
23 | if (isMatching(updateTuple)) { | ||
24 | listener.update(inputKey, updateTuple, isInsertion); | ||
25 | } | ||
26 | } | ||
27 | |||
28 | private boolean isMatching(ITuple tuple) { | ||
29 | if (seed == null) { | ||
30 | return true; | ||
31 | } | ||
32 | int size = seed.length; | ||
33 | for (int i = 0; i < size; i++) { | ||
34 | var filterElement = seed[i]; | ||
35 | if (filterElement != null && !filterElement.equals(tuple.get(i))) { | ||
36 | return false; | ||
37 | } | ||
38 | } | ||
39 | return true; | ||
40 | } | ||
41 | |||
42 | // Use <code>null</code> instead of an empty array to speed up comparisons. | ||
43 | @SuppressWarnings("squid:S1168") | ||
44 | private static Object[] seedToArray(ITuple seed) { | ||
45 | for (var element : seed.getElements()) { | ||
46 | if (element != null) { | ||
47 | return seed.getElements(); | ||
48 | } | ||
49 | } | ||
50 | return null; | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public boolean equals(Object obj) { | ||
55 | if (obj == this) return true; | ||
56 | if (obj == null || obj.getClass() != this.getClass()) return false; | ||
57 | var that = (RelationViewFilter) obj; | ||
58 | return Objects.equals(this.inputKey, that.inputKey) && Arrays.equals(this.seed, that.seed) && | ||
59 | Objects.equals(this.listener, that.listener); | ||
60 | } | ||
61 | |||
62 | @Override | ||
63 | public int hashCode() { | ||
64 | return Objects.hash(inputKey, Arrays.hashCode(seed), listener); | ||
65 | } | ||
66 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java new file mode 100644 index 00000000..e0d44e34 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java | |||
@@ -0,0 +1,40 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.update; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
7 | import tools.refinery.store.model.InterpretationListener; | ||
8 | import tools.refinery.store.query.view.RelationView; | ||
9 | import tools.refinery.store.query.view.TuplePreservingRelationView; | ||
10 | |||
11 | import java.util.ArrayList; | ||
12 | import java.util.List; | ||
13 | |||
14 | public abstract class RelationViewUpdateListener<T> implements InterpretationListener<T> { | ||
15 | private final List<RelationViewFilter> filters = new ArrayList<>(); | ||
16 | |||
17 | public void addFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { | ||
18 | filters.add(new RelationViewFilter(inputKey, seed, listener)); | ||
19 | } | ||
20 | |||
21 | public void removeFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { | ||
22 | filters.remove(new RelationViewFilter(inputKey, seed, listener)); | ||
23 | } | ||
24 | |||
25 | protected void processUpdate(Tuple tuple, boolean isInsertion) { | ||
26 | int size = filters.size(); | ||
27 | // Use a for loop instead of a for-each loop to avoid <code>Iterator</code> allocation overhead. | ||
28 | //noinspection ForLoopReplaceableByForEach | ||
29 | for (int i = 0; i < size; i++) { | ||
30 | filters.get(i).update(tuple, isInsertion); | ||
31 | } | ||
32 | } | ||
33 | |||
34 | public static <T> RelationViewUpdateListener<T> of(RelationView<T> relationView) { | ||
35 | if (relationView instanceof TuplePreservingRelationView<T> tuplePreservingRelationView) { | ||
36 | return new TuplePreservingRelationViewUpdateListener<>(tuplePreservingRelationView); | ||
37 | } | ||
38 | return new TupleChangingRelationViewUpdateListener<>(relationView); | ||
39 | } | ||
40 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java new file mode 100644 index 00000000..c17e826d --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java | |||
@@ -0,0 +1,35 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.update; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
4 | import tools.refinery.store.query.view.RelationView; | ||
5 | import tools.refinery.store.tuple.Tuple; | ||
6 | |||
7 | import java.util.Arrays; | ||
8 | |||
9 | public class TupleChangingRelationViewUpdateListener<T> extends RelationViewUpdateListener<T> { | ||
10 | private final RelationView<T> relationView; | ||
11 | |||
12 | TupleChangingRelationViewUpdateListener(RelationView<T> relationView) { | ||
13 | this.relationView = relationView; | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public void put(Tuple key, T fromValue, T toValue, boolean restoring) { | ||
18 | boolean fromPresent = relationView.filter(key, fromValue); | ||
19 | boolean toPresent = relationView.filter(key, toValue); | ||
20 | if (fromPresent) { | ||
21 | if (toPresent) { // value change | ||
22 | var fromArray = relationView.forwardMap(key, fromValue); | ||
23 | var toArray = relationView.forwardMap(key, toValue); | ||
24 | if (!Arrays.equals(fromArray, toArray)) { | ||
25 | processUpdate(Tuples.flatTupleOf(fromArray), false); | ||
26 | processUpdate(Tuples.flatTupleOf(toArray), true); | ||
27 | } | ||
28 | } else { // fromValue disappears | ||
29 | processUpdate(Tuples.flatTupleOf(relationView.forwardMap(key, fromValue)), false); | ||
30 | } | ||
31 | } else if (toPresent) { // toValue disappears | ||
32 | processUpdate(Tuples.flatTupleOf(relationView.forwardMap(key, toValue)), true); | ||
33 | } | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java new file mode 100644 index 00000000..9c3ef61c --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java | |||
@@ -0,0 +1,24 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.update; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
4 | import tools.refinery.store.query.view.TuplePreservingRelationView; | ||
5 | import tools.refinery.store.tuple.Tuple; | ||
6 | |||
7 | public class TuplePreservingRelationViewUpdateListener<T> extends RelationViewUpdateListener<T> { | ||
8 | private final TuplePreservingRelationView<T> view; | ||
9 | |||
10 | TuplePreservingRelationViewUpdateListener(TuplePreservingRelationView<T> view) { | ||
11 | this.view = view; | ||
12 | } | ||
13 | |||
14 | @Override | ||
15 | public void put(Tuple key, T fromValue, T toValue, boolean restoring) { | ||
16 | boolean fromPresent = view.filter(key, fromValue); | ||
17 | boolean toPresent = view.filter(key, toValue); | ||
18 | if (fromPresent == toPresent) { | ||
19 | return; | ||
20 | } | ||
21 | var translated = Tuples.flatTupleOf(view.forwardMap(key)); | ||
22 | processUpdate(translated, toPresent); | ||
23 | } | ||
24 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java deleted file mode 100644 index 6a1d06a9..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ModelUpdateListener.java +++ /dev/null | |||
@@ -1,112 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.viewupdate; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
6 | import tools.refinery.store.model.representation.AnyRelation; | ||
7 | import tools.refinery.store.query.view.AnyRelationView; | ||
8 | import tools.refinery.store.tuple.Tuple; | ||
9 | import tools.refinery.store.model.representation.Relation; | ||
10 | import tools.refinery.store.query.view.RelationView; | ||
11 | |||
12 | import java.util.HashMap; | ||
13 | import java.util.HashSet; | ||
14 | import java.util.Map; | ||
15 | import java.util.Set; | ||
16 | |||
17 | public class ModelUpdateListener { | ||
18 | /** | ||
19 | * Collections of Relations and their Views. | ||
20 | */ | ||
21 | private final Map<AnyRelation, Set<AnyRelationView>> relation2View; | ||
22 | |||
23 | /** | ||
24 | * Collection of Views and their buffers. | ||
25 | */ | ||
26 | private final Map<AnyRelationView, Set<ViewUpdateBuffer<?>>> view2Buffers; | ||
27 | |||
28 | public ModelUpdateListener(Set<AnyRelationView> relationViews) { | ||
29 | this.relation2View = new HashMap<>(); | ||
30 | this.view2Buffers = new HashMap<>(); | ||
31 | |||
32 | for (var relationView : relationViews) { | ||
33 | registerView(relationView); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | private void registerView(AnyRelationView view) { | ||
38 | AnyRelation relation = view.getRepresentation(); | ||
39 | |||
40 | // 1. register views to relations, if necessary | ||
41 | var views = relation2View.computeIfAbsent(relation, x -> new HashSet<>()); | ||
42 | views.add(view); | ||
43 | |||
44 | // 2. register notifier map to views, if necessary | ||
45 | view2Buffers.computeIfAbsent(view, x -> new HashSet<>()); | ||
46 | } | ||
47 | |||
48 | public boolean containsRelationView(AnyRelationView relationalKey) { | ||
49 | return view2Buffers.containsKey(relationalKey); | ||
50 | } | ||
51 | |||
52 | public <D> void addListener(IInputKey key, RelationView<D> relationView, ITuple seed, | ||
53 | IQueryRuntimeContextListener listener) { | ||
54 | if (view2Buffers.containsKey(relationView)) { | ||
55 | ViewUpdateTranslator<D> updateListener = new ViewUpdateTranslator<>(key, relationView, seed, listener); | ||
56 | ViewUpdateBuffer<D> updateBuffer = new ViewUpdateBuffer<>(updateListener); | ||
57 | view2Buffers.get(relationView).add(updateBuffer); | ||
58 | } else { | ||
59 | throw new IllegalArgumentException(); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | public void removeListener(IInputKey key, AnyRelationView relationView, ITuple seed, | ||
64 | IQueryRuntimeContextListener listener) { | ||
65 | if (view2Buffers.containsKey(relationView)) { | ||
66 | Set<ViewUpdateBuffer<?>> buffers = this.view2Buffers.get(relationView); | ||
67 | for (var buffer : buffers) { | ||
68 | if (buffer.getUpdateListener().equals(key, relationView, seed, listener)) { | ||
69 | // remove buffer and terminate immediately, or it will break iterator. | ||
70 | buffers.remove(buffer); | ||
71 | return; | ||
72 | } | ||
73 | } | ||
74 | } else { | ||
75 | throw new IllegalArgumentException("Relation view is not registered for updates"); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | public <D> void addUpdate(Relation<D> relation, Tuple key, D oldValue, D newValue) { | ||
80 | var views = this.relation2View.get(relation); | ||
81 | if (views == null) { | ||
82 | return; | ||
83 | } | ||
84 | for (var view : views) { | ||
85 | var buffers = this.view2Buffers.get(view); | ||
86 | for (var buffer : buffers) { | ||
87 | @SuppressWarnings("unchecked") | ||
88 | var typedBuffer = (ViewUpdateBuffer<D>) buffer; | ||
89 | typedBuffer.addChange(key, oldValue, newValue); | ||
90 | } | ||
91 | } | ||
92 | } | ||
93 | |||
94 | public boolean hasChanges() { | ||
95 | for (var bufferCollection : this.view2Buffers.values()) { | ||
96 | for (ViewUpdateBuffer<?> buffer : bufferCollection) { | ||
97 | if (buffer.hasChanges()) { | ||
98 | return true; | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | return false; | ||
103 | } | ||
104 | |||
105 | public void flush() { | ||
106 | for (var bufferCollection : this.view2Buffers.values()) { | ||
107 | for (ViewUpdateBuffer<?> buffer : bufferCollection) { | ||
108 | buffer.flush(); | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java deleted file mode 100644 index b9406018..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdate.java +++ /dev/null | |||
@@ -1,32 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.viewupdate; | ||
2 | |||
3 | import java.util.Arrays; | ||
4 | import java.util.Objects; | ||
5 | |||
6 | record ViewUpdate(Object[] tuple, boolean isInsertion) { | ||
7 | @Override | ||
8 | public int hashCode() { | ||
9 | final int prime = 31; | ||
10 | int result = 1; | ||
11 | result = prime * result + Arrays.deepHashCode(tuple); | ||
12 | result = prime * result + Objects.hash(isInsertion); | ||
13 | return result; | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public boolean equals(Object obj) { | ||
18 | if (this == obj) | ||
19 | return true; | ||
20 | if (obj == null) | ||
21 | return false; | ||
22 | if (getClass() != obj.getClass()) | ||
23 | return false; | ||
24 | ViewUpdate other = (ViewUpdate) obj; | ||
25 | return isInsertion == other.isInsertion && Arrays.deepEquals(tuple, other.tuple); | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public String toString() { | ||
30 | return "ViewUpdate [" + Arrays.toString(tuple) + "insertion= " + this.isInsertion + "]"; | ||
31 | } | ||
32 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java deleted file mode 100644 index 49f4c501..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateBuffer.java +++ /dev/null | |||
@@ -1,47 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.viewupdate; | ||
2 | |||
3 | import tools.refinery.store.tuple.Tuple; | ||
4 | |||
5 | import java.util.ArrayList; | ||
6 | import java.util.Arrays; | ||
7 | import java.util.List; | ||
8 | |||
9 | public class ViewUpdateBuffer<D> { | ||
10 | protected final ViewUpdateTranslator<D> updateListener; | ||
11 | |||
12 | protected final List<ViewUpdate> buffer = new ArrayList<>(); | ||
13 | |||
14 | public ViewUpdateBuffer(ViewUpdateTranslator<D> updateListener) { | ||
15 | this.updateListener = updateListener; | ||
16 | } | ||
17 | |||
18 | public ViewUpdateTranslator<D> getUpdateListener() { | ||
19 | return updateListener; | ||
20 | } | ||
21 | |||
22 | public boolean hasChanges() { | ||
23 | return !buffer.isEmpty(); | ||
24 | } | ||
25 | |||
26 | public void addChange(Tuple tuple, D oldValue, D newValue) { | ||
27 | if (oldValue != newValue) { | ||
28 | Object[] oldTuple = updateListener.isMatching(tuple, oldValue); | ||
29 | Object[] newTuple = updateListener.isMatching(tuple, newValue); | ||
30 | if (!Arrays.equals(oldTuple, newTuple)) { | ||
31 | if (oldTuple != null) { | ||
32 | buffer.add(new ViewUpdate(oldTuple, false)); | ||
33 | } | ||
34 | if (newTuple != null) { | ||
35 | buffer.add(new ViewUpdate(newTuple, true)); | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | |||
41 | public void flush() { | ||
42 | for (ViewUpdate viewChange : buffer) { | ||
43 | updateListener.processChange(viewChange); | ||
44 | } | ||
45 | buffer.clear(); | ||
46 | } | ||
47 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java deleted file mode 100644 index c324c84a..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/viewupdate/ViewUpdateTranslator.java +++ /dev/null | |||
@@ -1,73 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.viewupdate; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
7 | import tools.refinery.store.query.view.AnyRelationView; | ||
8 | import tools.refinery.store.query.view.RelationView; | ||
9 | import tools.refinery.store.tuple.Tuple; | ||
10 | |||
11 | import java.util.Objects; | ||
12 | |||
13 | public class ViewUpdateTranslator<D> { | ||
14 | private final IInputKey wrappedKey; | ||
15 | |||
16 | private final RelationView<D> key; | ||
17 | |||
18 | private final ITuple filter; | ||
19 | |||
20 | private final IQueryRuntimeContextListener listener; | ||
21 | |||
22 | public ViewUpdateTranslator(IInputKey wrappedKey, RelationView<D> key, ITuple filter, | ||
23 | IQueryRuntimeContextListener listener) { | ||
24 | super(); | ||
25 | this.wrappedKey = wrappedKey; | ||
26 | this.key = key; | ||
27 | this.filter = filter; | ||
28 | this.listener = listener; | ||
29 | } | ||
30 | |||
31 | public boolean equals(IInputKey wrappedKey, AnyRelationView relationView, ITuple seed, | ||
32 | IQueryRuntimeContextListener listener) { | ||
33 | return this.wrappedKey == wrappedKey && key == relationView && filter.equals(seed) && this.listener == listener; | ||
34 | } | ||
35 | |||
36 | public void processChange(ViewUpdate change) { | ||
37 | listener.update(wrappedKey, Tuples.flatTupleOf(change.tuple()), change.isInsertion()); | ||
38 | } | ||
39 | |||
40 | @SuppressWarnings("squid:S1168") | ||
41 | public Object[] isMatching(Tuple tuple, D value) { | ||
42 | if (!key.filter(tuple, value)) { | ||
43 | return null; | ||
44 | } | ||
45 | return isMatching(key.forwardMap(tuple, value), filter); | ||
46 | } | ||
47 | |||
48 | @SuppressWarnings("squid:S1168") | ||
49 | private Object[] isMatching(Object[] tuple, ITuple filter) { | ||
50 | for (int i = 0; i < filter.getSize(); i++) { | ||
51 | final Object filterObject = filter.get(i); | ||
52 | if (filterObject != null && !filterObject.equals(tuple[i])) { | ||
53 | return null; | ||
54 | } | ||
55 | } | ||
56 | return tuple; | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | public int hashCode() { | ||
61 | return Objects.hash(filter, key, listener); | ||
62 | } | ||
63 | |||
64 | @Override | ||
65 | public boolean equals(Object obj) { | ||
66 | if (this == obj) | ||
67 | return true; | ||
68 | if (!(obj instanceof ViewUpdateTranslator<?> other)) | ||
69 | return false; | ||
70 | return Objects.equals(filter, other.filter) && Objects.equals(key, other.key) | ||
71 | && Objects.equals(listener, other.listener); | ||
72 | } | ||
73 | } | ||