1 /*
2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.incubator.code;
27
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 import java.util.function.Function;
33
34 import static java.util.stream.Collectors.toList;
35
36 /**
37 * A context utilized when transforming code models.
38 * <p>
39 * The context holds a mapping of input values to output values, input blocks to output block builders,
40 * and input block references to output block references.
41 * Mappings are defined as an input model is transformed to produce an output model. Mappings are defined
42 * implicitly when an operation is transformed by copying, and can be explicitly defined when a transformation
43 * removes operations or adds new operations.
44 * <p>
45 * Associating an input code item to its corresponding output requires that an output's blocks are being built. An
46 * output value requires that its declaring block is being built. An output block builder requires that its block is
47 * being built. An output block reference requires that its target block is being built and all arguments declaring
48 * blocks are being built.
49 * <p>
50 * Unless otherwise specified the passing of a {@code null} argument to the methods of this interface results in a
51 * {@code NullPointerException}.
52 */
53 public final class CodeContext {
54
55 private static final Map<?, ?> EMPTY_MAP = Map.of();
56
57 @SuppressWarnings("unchecked")
58 private static <K, V> Map<K, V> emptyMap() {
59 return (Map<K, V>) EMPTY_MAP;
60 }
61
62 private final CodeContext parent;
63 private Map<Value, Value> valueMap;
64 private Map<Block, Block.Builder> blockMap;
65 private Map<Block.Reference, Block.Reference> successorMap;
66 private Map<Object, Object> propertiesMap;
67
68 private CodeContext(CodeContext that) {
69 this.parent = that;
70 this.blockMap = emptyMap();
71 this.valueMap = emptyMap();
72 this.successorMap = emptyMap();
73 this.propertiesMap = emptyMap();
74 }
75
76 /**
77 * {@return the parent context, otherwise {@code null} of there is no parent context.}
78 */
79 public CodeContext parent() {
80 return parent;
81 }
82
83 // Value mappings
84
85 /**
86 * {@return the output value mapped to the input value}
87 * <p>
88 * If this context is not isolated and there is no value mapping in this context then this method will return
89 * the result of calling {@code getValue} on the parent context, if present. Otherwise, if this context is isolated
90 * or there is no parent context, then there is no mapping.
91 *
92 * @param input the input value
93 * @throws IllegalArgumentException if there is no mapping
94 */
95 public Value getValue(Value input) {
96 Value output = getValueOrNull(input);
97 if (output != null) {
98 return output;
99 }
100 throw new IllegalArgumentException("No mapping for input value: " + input);
101 }
102
103 /**
104 * {@return the output value mapped to the input value or a default value if no mapping}
105 *
106 * @param input the input value
107 * @param defaultValue the default value to return if no mapping
108 */
109 public Value getValueOrDefault(Value input, Value defaultValue) {
110 Value output = getValueOrNull(input);
111 if (output != null) {
112 return output;
113 }
114 return defaultValue;
115 }
116
117 /**
118 * Returns a list of mapped output values by obtaining, in order, the output value for each element in the list
119 * of input values.
120 *
121 * @param inputs the list of input values
122 * @return a modifiable list of output values
123 * @throws IllegalArgumentException if an input value has no mapping
124 */
125 // @@@ If getValue is modified to return null then this method should fail on null
126 public List<Value> getValues(List<? extends Value> inputs) {
127 return inputs.stream().map(this::getValue).collect(toList());
128 }
129
130 private Value getValueOrNull(Value input) {
131 Objects.requireNonNull(input);
132
133 CodeContext p = this;
134 do {
135 Value output = p.valueMap.get(input);
136 if (output != null) {
137 return output;
138 }
139 p = p.parent;
140 } while (p != null);
141
142 return null;
143 }
144
145 /**
146 * Maps an input value to an output value.
147 * <p>
148 * Uses of the input value will be mapped to the output value when transforming.
149 *
150 * @param input the input value
151 * @param output the output value
152 * @throws IllegalArgumentException if the output value's declaring block is built
153 */
154 public void mapValue(Value input, Value output) {
155 Objects.requireNonNull(input);
156 Objects.requireNonNull(output);
157
158 if (output.isBuilt()) {
159 throw new IllegalArgumentException("Output value's declaring block is built: " + output);
160 }
161
162 if (valueMap == EMPTY_MAP) {
163 valueMap = new HashMap<>();
164 }
165 valueMap.put(input, output);
166 }
167
168 /**
169 * Maps an input value to an output value, if no such mapping exists.
170 * <p>
171 * Uses of the input value will be mapped to the output value when transforming.
172 *
173 * @param input the input value
174 * @param output the output value
175 * @return the previous mapped value, or null of there was no mapping.
176 * @throws IllegalArgumentException if the output value's declaring block is built
177 */
178 // @@@ Is this needed?
179 public Value mapValueIfAbsent(Value input, Value output) {
180 Objects.requireNonNull(input);
181 Objects.requireNonNull(output);
182
183 if (output.isBuilt()) {
184 throw new IllegalArgumentException("Output value's declaring block is built: " + output);
185 }
186
187 if (valueMap == EMPTY_MAP) {
188 valueMap = new HashMap<>();
189 }
190 return valueMap.putIfAbsent(input, output);
191 }
192
193 /**
194 * Maps the list of input values, in order, to the corresponding list of output values, up to the number of
195 * elements that is the minimum of the size of both lists.
196 * <p>
197 * Uses of an input value will be mapped to the corresponding output value when transforming.
198 *
199 * @param inputs the input values
200 * @param outputs the output values.
201 * @throws IllegalArgumentException if an output value's declaring block is built
202 */
203 public void mapValues(List<? extends Value> inputs, List<? extends Value> outputs) {
204 // @@@ sizes should be the same?
205 for (int i = 0; i < Math.min(inputs.size(), outputs.size()); i++) {
206 mapValue(inputs.get(i), outputs.get(i));
207 }
208 }
209
210
211 // Block mappings
212
213 /**
214 * {@return the output block builder mapped to the input block, otherwise null if no mapping}
215 *
216 * @param input the input block
217 */
218 // @@@ throw IllegalArgumentException if there is no mapping?
219 public Block.Builder getBlock(Block input) {
220 Objects.requireNonNull(input);
221
222 return blockMap.get(input);
223 }
224
225 /**
226 * Maps an input block to an output block builder.
227 * <p>
228 * Uses of the input block will be mapped to the output block builder when transforming.
229 *
230 * @param input the input block
231 * @param output the output block builder
232 * @throws IllegalArgumentException if the output block builder's block is built
233 */
234 public void mapBlock(Block input, Block.Builder output) {
235 Objects.requireNonNull(input);
236 Objects.requireNonNull(output);
237
238 if (output.target().isBuilt()) {
239 throw new IllegalArgumentException("Output block builder is built: " + output);
240 }
241
242 if (blockMap == EMPTY_MAP) {
243 blockMap = new HashMap<>();
244 }
245 blockMap.put(input, output);
246 }
247
248
249 // Successor mappings
250
251 /**
252 * {@return the output block reference mapped to the input block reference,
253 * otherwise null if no mapping}
254 *
255 * @param input the input reference
256 */
257 // @@@ throw IllegalArgumentException if there is no mapping?
258 public Block.Reference getSuccessor(Block.Reference input) {
259 Objects.requireNonNull(input);
260
261 return successorMap.get(input);
262 }
263
264 /**
265 * Maps an input block reference to an output block reference.
266 * <p>
267 * Uses of the input block reference will be mapped to the output block reference when transforming.
268 *
269 * @param input the input block reference
270 * @param output the output block reference
271 * @throws IllegalArgumentException if the output block reference's target block is built or any of the
272 * reference's arguments declaring blocks are built.
273 */
274 public void mapSuccessor(Block.Reference input, Block.Reference output) {
275 Objects.requireNonNull(input);
276 Objects.requireNonNull(output);
277
278 if (output.target.isBuilt()) {
279 throw new IllegalArgumentException("Output block reference's target block is built: " + output);
280 }
281
282 for (Value outputArgument : output.arguments()) {
283 if (outputArgument.isBuilt()) {
284 throw new IllegalArgumentException("Output block reference argument's declaring block is built: " + outputArgument);
285 }
286 }
287
288 if (successorMap == EMPTY_MAP) {
289 successorMap = new HashMap<>();
290 }
291 successorMap.put(input, output);
292 }
293
294 /**
295 * Returns a mapped output block reference, if present, otherwise creates a new, unmapped, reference from the input
296 * block reference.
297 * <p>
298 * A new, unmapped reference, is created by obtaining the mapped output block builder from the input reference's
299 * target block, and creating a successor from the output block builder with arguments that is the result of
300 * obtaining the mapped values from the input reference's arguments.
301 *
302 * @param input the input block reference
303 * @return the output block reference, if present, otherwise a created block reference
304 * @throws IllegalArgumentException if a new reference is to be created and there is no block mapping for the
305 * input's target block or there is no value mapping for an input's argument
306 */
307 public Block.Reference getSuccessorOrCreate(Block.Reference input) {
308 Block.Reference successor = getSuccessor(input);
309 if (successor != null) {
310 return successor;
311 }
312
313 // Create successor
314 Block.Builder outputBlock = getBlock(input.targetBlock());
315 if (outputBlock == null) {
316 throw new IllegalArgumentException("No mapping for input reference target block" + input.targetBlock());
317 }
318 return outputBlock.reference(getValues(input.arguments()));
319 }
320
321
322 // Properties mappings
323
324 /**
325 * {@return an object associated with a property key, or null if not associated.}
326 *
327 * @param key the property key
328 */
329 public Object getProperty(Object key) {
330 CodeContext p = this;
331 do {
332 Object value = p.propertiesMap.get(key);
333 if (value != null) {
334 return value;
335 }
336 p = p.parent;
337 } while (p != null);
338
339 return null;
340 }
341
342 /**
343 * Associates an object with a property key.
344 *
345 * @param key the property key
346 * @param value the associated object
347 * @return the current associated object, or null if not associated
348 */
349 public Object putProperty(Object key, Object value) {
350 if (propertiesMap == EMPTY_MAP) {
351 propertiesMap = new HashMap<>();
352 }
353 return propertiesMap.put(key, value);
354 }
355
356 /**
357 * If the property key is not already associated with an object, attempts to compute the object using the
358 * mapping function and associates it unless {@code null}.
359 *
360 * @param key the property key
361 * @param mappingFunction the mapping function
362 * @return the current (existing or computed) object associated with the property key,
363 * or null if the computed object is null
364 */
365 public Object computePropertyIfAbsent(Object key, Function<Object, Object> mappingFunction) {
366 if (propertiesMap == EMPTY_MAP) {
367 propertiesMap = new HashMap<>();
368 }
369 Object value = getProperty(key);
370 if (value != null) {
371 return value;
372 }
373 propertiesMap.put(key, value = mappingFunction.apply(key));
374 return value;
375 }
376
377
378 // Factories
379
380 /**
381 * {@return a new isolated context initialized with no mappings and no parent.}
382 */
383 public static CodeContext create() {
384 return new CodeContext(null);
385 }
386
387 /**
388 * {@return a new non-isolated context initialized with no mappings and a parent.}
389 * The returned context will query value and property mappings in its parent context
390 * if a query of its value and property mappings yields no result, and so on until
391 * a context has no parent context.
392 * <p>
393 * The returned context only queries its own block and block reference mappings
394 * and does not query the mappings in any ancestor context.
395 *
396 * @param parent the parent code context
397 */
398 public static CodeContext create(CodeContext parent) {
399 return new CodeContext(parent);
400 }
401 }