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.*;
29 import java.util.function.Function;
30 import java.util.stream.Collectors;
31
32 import static java.util.stream.Collectors.toList;
33
34 /**
35 * A context used when building and transforming code models.
36 * <p>
37 * A code context records correspondence between input code items and outputs during building and transformation of code
38 * models. It holds mappings from:
39 * <ul>
40 * <li>
41 * input values to output values;
42 * <li>
43 * input blocks to output block builders; and
44 * <li>
45 * input block references to output block references.
46 * </ul>
47 * <p>
48 * Mappings are partial, since a mapping may not exist for a given input code item. Two forms of mapping lookup are
49 * provided. One form returns the mapped output, if present, otherwise throws. The other form returns an optional
50 * containing the mapped output, if present, otherwise an empty optional.
51 * <p>
52 * A code context may have a parent context, in which case it is referred to as a <i>child</i> context, otherwise it is
53 * referred to as a <i>root</i> context.
54 * <p>
55 * Value mappings are looked up first in the current context and, if absent, in the parent context, if present, and so
56 * on until a mapping is found or there is no parent context. Block and block reference mappings are local to the
57 * current context and are not looked up in parent contexts. Mappings are always added to the current context.
58 * <p>
59 * The requirements for mapping an input code item depend on the kind of output:
60 * <ul>
61 * <li>
62 * an output value requires that its declaring block is being built;
63 * <li>
64 * an output block builder requires that its block is being built;
65 * <li>
66 * an output block reference requires that its target block is being built, and each of the output block reference's
67 * arguments requires that its declaring block is being built.
68 * </ul>
69 * <p>
70 * A code context also supports properties that map arbitrary non-{@code null} keys mapped to arbitrary
71 * non-{@code null} values. Properties are looked up first in the current context and, if absent, in the parent context,
72 * and so on until a property is found or there is no parent context. Properties are always added to the current
73 * context.
74 * <p>
75 * Code contexts are not thread-safe.
76 * <p>
77 * Unless otherwise specified the passing of a {@code null} argument to the methods of this class results in a
78 * {@code NullPointerException}.
79 */
80 public final class CodeContext {
81
82 private static final Map<?, ?> EMPTY_MAP = Map.of();
83
84 @SuppressWarnings("unchecked")
85 private static <K, V> Map<K, V> emptyMap() {
86 return (Map<K, V>) EMPTY_MAP;
87 }
88
89 private final CodeContext parent;
90 private Map<Value, Value> valueMap;
91 private Map<Block, Block.Builder> blockMap;
92 private Map<Block.Reference, Block.Reference> referenceMap;
93 private Map<Object, Object> propertiesMap;
94
95 private CodeContext(CodeContext that) {
96 this.parent = that;
97 this.blockMap = emptyMap();
98 this.valueMap = emptyMap();
99 this.referenceMap = emptyMap();
100 this.propertiesMap = emptyMap();
101 }
102
103 /**
104 * {@return the parent context, otherwise {@code null} if this is a root context.}
105 */
106 public CodeContext parent() {
107 return parent;
108 }
109
110 // Value mappings
111
112 /**
113 * {@return the output value mapped to the given input value}
114 * <p>
115 * The output value is looked up first in this context and, if absent, in the parent context, if present, and so on
116 * until a mapping is found or there is no parent context.
117 *
118 * @param input the input value
119 * @throws IllegalArgumentException if there is no mapping
120 * @see #queryValue(Value)
121 * @see #mapValue(Value, Value)
122 */
123 public Value getValue(Value input) {
124 Value output = getValueOrNull(input);
125 if (output != null) {
126 return output;
127 }
128 throw new IllegalArgumentException("No mapping for input value: " + input);
129 }
130
131 /**
132 * Queries the output value for the given input value.
133 * <p>
134 * If such an output value is present, this method returns an optional containing that value.
135 * Otherwise, this method returns an empty optional.
136 * <p>
137 * The output value is looked up first in this context and, if absent, in the parent context, if present, and so on
138 * until a mapping is found or there is no parent context.
139 *
140 * @param input the input value
141 * @return an optional containing the output value mapped to the given input value, otherwise an empty optional
142 * @see #getValue(Value)
143 */
144 public Optional<Value> queryValue(Value input) {
145 Objects.requireNonNull(input);
146
147 return Optional.ofNullable(getValueOrNull(input));
148 }
149
150 /**
151 * Returns output values mapped to the given input values, in order.
152 * <p>
153 * The output value for each input value is obtained using {@link #getValue(Value)}.
154 *
155 * @param inputs the list of input values
156 * @return a modifiable list of output values mapped to the given input values, in order
157 * @throws IllegalArgumentException if an input value has no mapping
158 * @see #getValue(Value)
159 */
160 public List<Value> getValues(List<? extends Value> inputs) {
161 // @@@ Consider making this an unmodifiable list
162 // There are usages that currently rely on a modifiable list
163 return inputs.stream().map(this::getValue).collect(Collectors.toCollection(ArrayList::new));
164 }
165
166 private Value getValueOrNull(Value input) {
167 Objects.requireNonNull(input);
168
169 CodeContext p = this;
170 do {
171 Value output = p.valueMap.get(input);
172 if (output != null) {
173 return output;
174 }
175 p = p.parent;
176 } while (p != null);
177
178 return null;
179 }
180
181 /**
182 * Maps the given input value to the given output value.
183 * <p>
184 * The mapping is added only to this context.
185 *
186 * @param input the input value
187 * @param output the output value
188 * @throws IllegalArgumentException if the output value's declaring block is built
189 * @see #getValue(Value)
190 */
191 public void mapValue(Value input, Value output) {
192 Objects.requireNonNull(input);
193 Objects.requireNonNull(output);
194
195 if (output.isBuilt()) {
196 throw new IllegalArgumentException("Output value's declaring block is built: " + output);
197 }
198
199 if (valueMap == EMPTY_MAP) {
200 valueMap = new HashMap<>();
201 }
202 valueMap.put(input, output);
203 }
204
205 /**
206 * Maps the given input values, in order, to the given output values.
207 * <p>
208 * The mappings are added only to this context.
209 *
210 * @param inputs the input values
211 * @param outputs the output values
212 * @throws IllegalArgumentException if an output value's declaring block is built
213 * @throws IllegalArgumentException if the number of input values differs from the number of output values
214 * @see #mapValue(Value, Value)
215 * @see #mapValuePrefix(List, List)
216 */
217 public void mapValues(List<? extends Value> inputs, List<? extends Value> outputs) {
218 if (inputs.size() != outputs.size()) {
219 throw new IllegalArgumentException("The number of input values differs from the number of output values");
220 }
221
222 for (int i = 0; i < outputs.size(); i++) {
223 mapValue(inputs.get(i), outputs.get(i));
224 }
225 }
226
227 /**
228 * Maps a prefix of the given input values, in order, to the given output values.
229 * <p>
230 * The mappings are added only to this context.
231 *
232 * @param inputs the input values
233 * @param outputs the output values
234 * @throws IllegalArgumentException if an output value's declaring block is built
235 * @throws IllegalArgumentException if there are more output values than input values
236 * @see #mapValue(Value, Value)
237 * @see #mapValues(List, List)
238 */
239 public void mapValuePrefix(List<? extends Value> inputs, List<? extends Value> outputs) {
240 if (outputs.size() > inputs.size()) {
241 throw new IllegalArgumentException("More output values than input values");
242 }
243 for (int i = 0; i < outputs.size(); i++) {
244 mapValue(inputs.get(i), outputs.get(i));
245 }
246 }
247
248 // Block mappings
249
250 /**
251 * {@return the output block builder mapped to the given input block}
252 * <p>
253 * The output block builder is looked up only in this context.
254 *
255 * @param input the input block
256 * @throws IllegalArgumentException if there is no mapping
257 * @see #queryBlock(Block)
258 * @see #mapBlock(Block, Block.Builder)
259 */
260 public Block.Builder getBlock(Block input) {
261 Objects.requireNonNull(input);
262
263 Block.Builder output = blockMap.get(input);
264 if (output == null) {
265 throw new IllegalArgumentException("No mapping for input block: " + input);
266 }
267
268 return output;
269 }
270
271 /**
272 * Queries the output block builder for the given input block.
273 * <p>
274 * The output block builder is looked up only in this context.
275 *
276 * @param input the input block
277 * @return an optional containing the output block builder mapped to the given input block, otherwise an empty
278 * optional
279 */
280 public Optional<Block.Builder> queryBlock(Block input) {
281 Objects.requireNonNull(input);
282
283 return Optional.ofNullable(blockMap.get(input));
284 }
285
286 /**
287 * Maps the given input block to the given output block builder.
288 * <p>
289 * The mapping is added only to this context.
290 *
291 * @param input the input block
292 * @param output the output block builder
293 * @throws IllegalArgumentException if the output block builder's block is built
294 * @see #getBlock(Block)
295 */
296 public void mapBlock(Block input, Block.Builder output) {
297 Objects.requireNonNull(input);
298 Objects.requireNonNull(output);
299
300 if (output.target().isBuilt()) {
301 throw new IllegalArgumentException("Output block builder is built: " + output);
302 }
303
304 if (blockMap == EMPTY_MAP) {
305 blockMap = new HashMap<>();
306 }
307 blockMap.put(input, output);
308 }
309
310
311 // Derived body mappings
312
313 /**
314 * Queries the output body builder for the given input body.
315 * <p>
316 * This method queries the output block builder for the given input body's {@link Body#entryBlock() entry} block. If
317 * such a block builder is present, this method returns an optional containing that block builder's
318 * {@link Block.Builder#parentBody() parent} body builder. Otherwise, this method returns an empty optional.
319 * <p>
320 * The output block builder is looked up only in this context.
321 *
322 * @param input the input body
323 * @return an optional containing the output body builder corresponding to the given input body, otherwise an empty
324 * optional
325 * @see #queryBlock(Block)
326 */
327 public Optional<Body.Builder> queryBody(Body input) {
328 return queryBlock(input.entryBlock()).map(Block.Builder::parentBody);
329 }
330
331
332 // Reference mappings
333
334 /**
335 * {@return the output block reference mapped to the given input block reference}
336 * <p>
337 * The output block reference is looked up only in this context.
338 *
339 * @param input the input reference
340 * @throws IllegalArgumentException if there is no mapping
341 * @see #queryReference(Block.Reference)
342 * @see #mapReference(Block.Reference, Block.Reference)
343 */
344 public Block.Reference getReference(Block.Reference input) {
345 Objects.requireNonNull(input);
346
347 Block.Reference output = referenceMap.get(input);
348 if (output == null) {
349 throw new IllegalArgumentException("No mapping for input block reference: " + input);
350 }
351
352 return output;
353 }
354
355 /**
356 * Queries the output block reference for the given input block reference.
357 * <p>
358 * The output block reference is looked up only in this context.
359 *
360 * @param input the input block reference
361 * @return an optional containing the output block reference mapped to the given input block reference, otherwise an
362 * empty optional
363 * @see #getReference(Block.Reference)
364 */
365 public Optional<Block.Reference> queryReference(Block.Reference input) {
366 Objects.requireNonNull(input);
367
368 return Optional.ofNullable(referenceMap.get(input));
369 }
370
371 /**
372 * Maps the given input block reference to the given output block reference.
373 * <p>
374 * The mapping is added only to this context.
375 *
376 * @param input the input block reference
377 * @param output the output block reference
378 * @throws IllegalArgumentException if the output block reference's target block is built or any of the
379 * reference's arguments declaring blocks are built.
380 * @see #getReference(Block.Reference)
381 */
382 public void mapReference(Block.Reference input, Block.Reference output) {
383 Objects.requireNonNull(input);
384 Objects.requireNonNull(output);
385
386 if (output.target.isBuilt()) {
387 throw new IllegalArgumentException("Output block reference's target block is built: " + output);
388 }
389
390 for (Value outputArgument : output.arguments()) {
391 if (outputArgument.isBuilt()) {
392 throw new IllegalArgumentException("Output block reference argument's declaring block is built: " + outputArgument);
393 }
394 }
395
396 if (referenceMap == EMPTY_MAP) {
397 referenceMap = new HashMap<>();
398 }
399 referenceMap.put(input, output);
400 }
401
402 /**
403 * Returns the output block reference mapped to the given input block reference, if present, otherwise creates
404 * and returns a new output block reference from the input block reference.
405 * <p>
406 * If a mapping for the input block reference is present in this context, the mapped output block reference is
407 * returned. Otherwise, a new output block reference is created by obtaining the output block builder mapped to the
408 * input reference's target block, and creating a reference from the output block builder with arguments that are
409 * the output values mapped to the input reference's arguments. The newly created output block reference is not
410 * added to this context.
411 * <p>
412 * The output block reference and the output block builder are looked up only in this context. The output values for
413 * the input reference's arguments are looked up as specified by {@link #getValues(List)}.
414 *
415 * @param input the input block reference
416 * @return the output block reference, if present, otherwise a newly created output block reference
417 * @throws IllegalArgumentException if a new reference is to be created and there is no block mapping for the
418 * input's target block or there is no value mapping for an input's argument
419 * @see #queryReference(Block.Reference)
420 */
421 public Block.Reference getReferenceOrCreate(Block.Reference input) {
422 Optional<Block.Reference> optionalOutput = queryReference(input);
423 if (optionalOutput.isPresent()) {
424 return optionalOutput.get();
425 }
426
427 // Create reference
428 Block.Builder outputBlock = blockMap.get(input.targetBlock());
429 if (outputBlock == null) {
430 throw new IllegalArgumentException("No mapping for input reference target block" + input.targetBlock());
431 }
432 return outputBlock.reference(getValues(input.arguments()));
433 }
434
435
436 // Properties mappings
437
438 /**
439 * {@return an object associated with a property key, or {@code null} if not associated.}
440 * <p>
441 * The property is looked up first in this context and, if absent, in the parent context, if present, and so on
442 * until a mapping is found or there is no parent context.
443 *
444 * @param key the property key
445 * @see #putProperty(Object, Object)
446 */
447 public Object getProperty(Object key) {
448 Objects.requireNonNull(key);
449
450 CodeContext p = this;
451 do {
452 Object value = p.propertiesMap.get(key);
453 if (value != null) {
454 return value;
455 }
456 p = p.parent;
457 } while (p != null);
458
459 return null;
460 }
461
462 /**
463 * Associates an object with a property key.
464 * <p>
465 * The property is added only to this context.
466 *
467 * @param key the property key
468 * @param value the associated object
469 * @return the previous associated object, or {@code null} if not associated
470 * @see #getProperty(Object)
471 */
472 public Object putProperty(Object key, Object value) {
473 Objects.requireNonNull(key);
474 Objects.requireNonNull(value);
475
476 if (propertiesMap == EMPTY_MAP) {
477 propertiesMap = new HashMap<>();
478 }
479 return propertiesMap.put(key, value);
480 }
481
482 /**
483 * If the property key is not already associated with an object, attempts to compute the object using the
484 * mapping function and associates it.
485 * <p>
486 * This method first obtains the property value using {@link #getProperty(Object)}. If no property is found, a new
487 * non-{@code null} property value is computed and added only to this context.
488 *
489 * @param key the property key
490 * @param mappingFunction the mapping function
491 * @return the current (existing or computed) object associated with the property key
492 * @see #getProperty(Object)
493 * @see #putProperty(Object, Object)
494 */
495 public Object computePropertyIfAbsent(Object key, Function<Object, Object> mappingFunction) {
496 Objects.requireNonNull(key);
497 Objects.requireNonNull(mappingFunction);
498
499 if (propertiesMap == EMPTY_MAP) {
500 propertiesMap = new HashMap<>();
501 }
502 Object value = getProperty(key);
503 if (value != null) {
504 return value;
505 }
506 value = Objects.requireNonNull(mappingFunction.apply(key));
507 propertiesMap.put(key, value);
508 return value;
509 }
510
511
512 // Factories
513
514 /**
515 * {@return a new root context initialized with no local mappings, no properties, and no parent context.}
516 * @see #create(CodeContext)
517 */
518 public static CodeContext create() {
519 return new CodeContext(null);
520 }
521
522 /**
523 * {@return a new child context initialized with the given parent context, no local mappings, and no properties.}
524 * <p>
525 * The returned context looks up value mappings and properties first in itself, and then in its parent context,
526 * and so on, until a mapping is found or there is no parent context.
527 * <p>
528 * Block and block reference mappings are local to the returned context and are not looked up in parent contexts.
529 * <p>
530 * Mappings and properties added to the returned context are added only to that context.
531 *
532 * @param parent the parent code context
533 */
534 public static CodeContext create(CodeContext parent) {
535 Objects.requireNonNull(parent);
536 return new CodeContext(parent);
537 }
538 }