aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryEvaluationHint.java
blob: eab921287986d2b6c66f2f0171e8f256a660fc2f (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
/*******************************************************************************
 * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro
 * 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.backend;

import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import tools.refinery.viatra.runtime.matchers.util.Preconditions;

/**
 * Provides VIATRA Query with additional hints on how a query should be evaluated. The same hint can be provided to multiple queries. 
 * 
 * <p> This class is immutable. Overriding options will create a new instance.
 * 
 * <p>
 * Here be dragons: for advanced users only.
 * 
 * @author Bergmann Gabor
 *
 */
public class QueryEvaluationHint {
    
    /**
     * @since 2.0
     *
     */
    public enum BackendRequirement {
        /**
         * The current hint does not specify any backend requirement
         */
        UNSPECIFIED,
        /**
         * The current hint specifies that the default search backend of the engine should be used
         */
        DEFAULT_SEARCH,
        /**
         * The current hint specifies that the default caching backend of the engine should be used
         */
        DEFAULT_CACHING,
        /**
         * The current hint specifies that a specific backend is to be used
         */
        SPECIFIC
    }
    
    final IQueryBackendFactory queryBackendFactory; 
    final Map<QueryHintOption<?>, Object> backendHintSettings;
    final BackendRequirement requirement;

    /**
     * Specifies the suggested query backend requirements, and value settings for additional backend-specific options.
     * 
     * <p>
     * The backend requirement type must not be {@link BackendRequirement#SPECIFIC} - for that case, use the constructor
     * {@link #QueryEvaluationHint(Map, IQueryBackendFactory)}.
     * 
     * @param backendHintSettings
     *            if non-null, each entry in the map overrides backend-specific options regarding query evaluation
     *            (null-valued map entries permitted to erase hints); passing null means default options associated with
     *            the query
     * @param backendRequirementType
     *            defines the kind of backend requirement
     * @since 2.0
     */
    public QueryEvaluationHint(Map<QueryHintOption<?>, Object> backendHintSettings, BackendRequirement backendRequirementType) {
        super();
        Preconditions.checkArgument(backendRequirementType != null, "Specific requirement needs to be set");
        Preconditions.checkArgument(backendRequirementType != BackendRequirement.SPECIFIC, "Specific backend requirement needs providing a corresponding backend type");
        this.queryBackendFactory = null;
        this.requirement = backendRequirementType; 
        this.backendHintSettings = (backendHintSettings == null) 
                ? Collections.<QueryHintOption<?>, Object> emptyMap()
                : new HashMap<>(backendHintSettings);
    }
    
    /**
     * Specifies the suggested query backend, and value settings for additional backend-specific options. The first
     * parameter can be null; if the second parameter is null, it is expected that the other constructor is called
     * instead with a {@link BackendRequirement#UNSPECIFIED} parameter.
     * 
     * @param backendHintSettings
     *            if non-null, each entry in the map overrides backend-specific options regarding query evaluation
     *            (null-valued map entries permitted to erase hints); passing null means default options associated with
     *            the query
     * @param queryBackendFactory
     *            overrides the query evaluator algorithm; passing null retains the default algorithm associated with
     *            the query
     * @since 1.5
     */
    public QueryEvaluationHint(
            Map<QueryHintOption<?>, Object> backendHintSettings, 
            IQueryBackendFactory queryBackendFactory) {
        super();
        this.queryBackendFactory = queryBackendFactory;
        this.requirement = (queryBackendFactory == null) ? BackendRequirement.UNSPECIFIED : BackendRequirement.SPECIFIC; 
        this.backendHintSettings = (backendHintSettings == null) 
                ? Collections.<QueryHintOption<?>, Object> emptyMap()
                : new HashMap<>(backendHintSettings);
    }
    
    /**
     * Returns the backend requirement described by this hint. If a specific backend is required, that can be queried by {@link #getQueryBackendFactory()}.
     * @since 2.0
     */
    public BackendRequirement getQueryBackendRequirementType() {
        return requirement;
    }
    
    /**
     * A suggestion for choosing the query evaluator algorithm.
     * 
     * <p>
     * Returns null iff {@link #getQueryBackendRequirementType()} does not return {@link BackendRequirement#SPECIFIC};
     * in such cases a corresponding default backend is selected inside the engine
     */
    public IQueryBackendFactory getQueryBackendFactory() {
        return queryBackendFactory;
    }
    
    /**
     * Each entry in the immutable map overrides backend-specific options regarding query evaluation. 
     * 
     * <p>The map is non-null, even if empty. 
     * Null-valued map entries are also permitted to erase hints via {@link #overrideBy(QueryEvaluationHint)}. 
     * 
     * @since 1.5
     */
    public Map<QueryHintOption<?>, Object> getBackendHintSettings() {
        return backendHintSettings;
    }


    /**
     * Override values in this hint and return a consolidated instance.
     * 
     * @since 1.4
     */
    public QueryEvaluationHint overrideBy(QueryEvaluationHint overridingHint){
        if (overridingHint == null)
            return this;
        
        BackendRequirement overriddenRequirement = this.getQueryBackendRequirementType();
        if (overridingHint.getQueryBackendRequirementType() != BackendRequirement.UNSPECIFIED) {
            overriddenRequirement = overridingHint.getQueryBackendRequirementType();
        }
        Map<QueryHintOption<?>, Object> hints = new HashMap<>(this.getBackendHintSettings());
        if (overridingHint.getBackendHintSettings() != null) {
            hints.putAll(overridingHint.getBackendHintSettings());
        }
        if (overriddenRequirement == BackendRequirement.SPECIFIC) {
            IQueryBackendFactory factory = this.getQueryBackendFactory();
            if (overridingHint.getQueryBackendFactory() != null) {
                factory = overridingHint.getQueryBackendFactory();
            }
            return new QueryEvaluationHint(hints, factory);
        } else {
            return new QueryEvaluationHint(hints, overriddenRequirement);
        }
    }
    
    /**
     * Returns whether the given hint option is overridden.
     * @since 1.5
     */
    public boolean isOptionOverridden(QueryHintOption<?> option) {
        return getBackendHintSettings().containsKey(option);
    }
    
    /**
     * Returns the value of the given hint option from the given hint collection, or null if not defined.
     * @since 1.5
     */
    @SuppressWarnings("unchecked")
    public <HintValue> HintValue getValueOrNull(QueryHintOption<HintValue> option) {
        return (HintValue) getBackendHintSettings().get(option);
    }
    
    /**
     * Returns the value of the given hint option from the given hint collection, or the default value if not defined.
     * Intended to be called by backends to find out the definitive value that should be considered.
     * @since 1.5
     */
    public <HintValue> HintValue getValueOrDefault(QueryHintOption<HintValue> option) {
        return option.getValueOrDefault(this);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(backendHintSettings, queryBackendFactory, requirement);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        QueryEvaluationHint other = (QueryEvaluationHint) obj;
        return Objects.equals(backendHintSettings, other.backendHintSettings)
               &&
               Objects.equals(queryBackendFactory, other.queryBackendFactory)
               &&
               Objects.equals(requirement, other.requirement)
        ;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        
        if (getQueryBackendFactory() != null)
            sb.append("backend: ").append(getQueryBackendFactory().getBackendClass().getSimpleName());
        if (! backendHintSettings.isEmpty()) {
            sb.append("hints: ");
            if(backendHintSettings instanceof AbstractMap){
                sb.append(backendHintSettings.toString());
            } else {
                // we have to iterate on the contents
                
                String joinedHintMap = backendHintSettings.entrySet().stream()
                        .map(setting -> setting.getKey() + "=" + setting.getValue()).collect(Collectors.joining(", "));
                sb.append('{').append(joinedHintMap).append('}');
            }            
        }
        
        final String result = sb.toString();
        return result.isEmpty() ? "defaults" : result;
    }
}