1 /*
2 * Copyright (c) 2024, 2026, 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 output value is mapped to this input value in the code context\n"
129 + input.block.diagnosticText(input, "unmapped input value"));
130 }
131
132 /**
133 * Queries the output value for the given input value.
134 * <p>
135 * If such an output value is present, this method returns an optional containing that value.
136 * Otherwise, this method returns an empty optional.
137 * <p>
138 * The output value is looked up first in this context and, if absent, in the parent context, if present, and so on
139 * until a mapping is found or there is no parent context.
140 *
141 * @param input the input value
142 * @return an optional containing the output value mapped to the given input value, otherwise an empty optional
143 * @see #getValue(Value)
144 */
145 public Optional<Value> queryValue(Value input) {
146 Objects.requireNonNull(input);
147
148 return Optional.ofNullable(getValueOrNull(input));
149 }
150
151 /**
152 * Returns output values mapped to the given input values, in order.
153 * <p>
154 * The output value for each input value is obtained using {@link #getValue(Value)}.
155 *
156 * @param inputs the list of input values
157 * @return a modifiable list of output values mapped to the given input values, in order
158 * @throws IllegalArgumentException if an input value has no mapping
159 * @see #getValue(Value)
160 */
161 public List<Value> getValues(List<? extends Value> inputs) {
162 // @@@ Consider making this an unmodifiable list
163 // There are usages that currently rely on a modifiable list
164 return inputs.stream().map(this::getValue).collect(Collectors.toCollection(ArrayList::new));
165 }
166
167 private Value getValueOrNull(Value input) {
168 Objects.requireNonNull(input);
169
170 CodeContext p = this;
171 do {
172 Value output = p.valueMap.get(input);
173 if (output != null) {
174 return output;
175 }
176 p = p.parent;
177 } while (p != null);
178
179 return null;
180 }
181
182 /**
183 * Maps the given input value to the given output value.
184 * <p>
185 * The mapping is added only to this context.
186 *
187 * @param input the input value
188 * @param output the output value
189 * @throws IllegalArgumentException if the output value's declaring block is built
190 * @see #getValue(Value)
191 */
192 public void mapValue(Value input, Value output) {
193 Objects.requireNonNull(input);
194 Objects.requireNonNull(output);
195
196 if (output.isBuilt()) {
197 throw new IllegalArgumentException("Output value's declaring block is built\n"
198 + output.block.diagnosticText(output, "output value"));
199 }
200
201 if (valueMap == EMPTY_MAP) {
202 valueMap = new HashMap<>();
203 }
204 valueMap.put(input, output);
205 }
206
207 /**
208 * Maps the given input values, in order, to the given output values.
209 * <p>
210 * The mappings are added only to this context.
211 *
212 * @param inputs the input values
213 * @param outputs the output values
214 * @throws IllegalArgumentException if an output value's declaring block is built
215 * @throws IllegalArgumentException if the number of input values differs from the number of output values
216 * @see #mapValue(Value, Value)
217 * @see #mapValuePrefix(List, List)
218 */
219 public void mapValues(List<? extends Value> inputs, List<? extends Value> outputs) {
220 if (inputs.size() != outputs.size()) {
221 throw new IllegalArgumentException("The number of input values differs from the number of output values");
222 }
223
224 for (int i = 0; i < outputs.size(); i++) {
225 mapValue(inputs.get(i), outputs.get(i));
226 }
227 }
228
229 /**
230 * Maps a prefix of the given input values, in order, to the given output values.
231 * <p>
232 * The mappings are added only to this context.
233 *
234 * @param inputs the input values
235 * @param outputs the output values
236 * @throws IllegalArgumentException if an output value's declaring block is built
237 * @throws IllegalArgumentException if there are more output values than input values
238 * @see #mapValue(Value, Value)
239 * @see #mapValues(List, List)
240 */
241 public void mapValuePrefix(List<? extends Value> inputs, List<? extends Value> outputs) {
242 if (outputs.size() > inputs.size()) {
243 throw new IllegalArgumentException("More output values than input values");
244 }
245 for (int i = 0; i < outputs.size(); i++) {
246 mapValue(inputs.get(i), outputs.get(i));
247 }
248 }
249
250 // Block mappings
251
252 /**
253 * {@return the output block builder mapped to the given input block}
254 * <p>
255 * The output block builder is looked up only in this context.
256 *
257 * @param input the input block
258 * @throws IllegalArgumentException if there is no mapping
259 * @see #queryBlock(Block)
260 * @see #mapBlock(Block, Block.Builder)
261 */
262 public Block.Builder getBlock(Block input) {
263 Objects.requireNonNull(input);
264
265 Block.Builder output = blockMap.get(input);
266 if (output == null) {
267 throw new IllegalArgumentException("No mapping for input block\n"
268 + input.diagnosticText("input block"));
269 }
270
271 return output;
272 }
273
274 /**
275 * Queries the output block builder for the given input block.
276 * <p>
277 * The output block builder is looked up only in this context.
278 *
279 * @param input the input block
280 * @return an optional containing the output block builder mapped to the given input block, otherwise an empty
281 * optional
282 * @see #getBlock(Block)
283 */
284 public Optional<Block.Builder> queryBlock(Block input) {
285 Objects.requireNonNull(input);
286
287 return Optional.ofNullable(blockMap.get(input));
288 }
289
290 /**
291 * Maps the given input block to the given output block builder.
292 * <p>
293 * The mapping is added only to this context.
294 *
295 * @param input the input block
296 * @param output the output block builder
297 * @throws IllegalArgumentException if the output block builder's block is built
298 * @see #getBlock(Block)
299 */
300 public void mapBlock(Block input, Block.Builder output) {
301 Objects.requireNonNull(input);
302 Objects.requireNonNull(output);
303
304 if (output.target().isBuilt()) {
305 throw new IllegalArgumentException("Output block builder is built\n"
306 + output.target().diagnosticText("output block"));
307 }
308
309 if (blockMap == EMPTY_MAP) {
310 blockMap = new HashMap<>();
311 }
312 blockMap.put(input, output);
313 }
314
315
316 // Derived body mappings
317
318 /**
319 * Queries the output body builder for the given input body.
320 * <p>
321 * This method queries the output block builder for the given input body's {@link Body#entryBlock() entry} block. If
322 * such a block builder is present, this method returns an optional containing that block builder's
323 * {@link Block.Builder#parentBody() parent} body builder. Otherwise, this method returns an empty optional.
324 * <p>
325 * The output block builder is looked up only in this context.
326 *
327 * @param input the input body
328 * @return an optional containing the output body builder corresponding to the given input body, otherwise an empty
329 * optional
330 * @see #queryBlock(Block)
331 */
332 public Optional<Body.Builder> queryBody(Body input) {
333 return queryBlock(input.entryBlock()).map(Block.Builder::parentBody);
334 }
335
336
337 // Reference mappings
338
339 /**
340 * {@return the output block reference mapped to the given input block reference}
341 * <p>
342 * The output block reference is looked up only in this context.
343 *
344 * @param input the input reference
345 * @throws IllegalArgumentException if there is no mapping
346 * @see #queryReference(Block.Reference)
347 * @see #mapReference(Block.Reference, Block.Reference)
348 */
349 public Block.Reference getReference(Block.Reference input) {
350 Objects.requireNonNull(input);
351
352 Block.Reference output = referenceMap.get(input);
353 if (output == null) {
354 throw new IllegalArgumentException("No mapping for input block reference\n"
355 + input.target.diagnosticText("reference target"));
356 }
357
358 return output;
359 }
360
361 /**
362 * Queries the output block reference for the given input block reference.
363 * <p>
364 * The output block reference is looked up only in this context.
365 *
366 * @param input the input block reference
367 * @return an optional containing the output block reference mapped to the given input block reference, otherwise an
368 * empty optional
369 * @see #getReference(Block.Reference)
370 */
371 public Optional<Block.Reference> queryReference(Block.Reference input) {
372 Objects.requireNonNull(input);
373
374 return Optional.ofNullable(referenceMap.get(input));
375 }
376
377 /**
378 * Maps the given input block reference to the given output block reference.
379 * <p>
380 * The mapping is added only to this context.
381 *
382 * @param input the input block reference
383 * @param output the output block reference
384 * @throws IllegalArgumentException if the output block reference's target block is built or any of the
385 * reference's arguments declaring blocks are built.
386 * @see #getReference(Block.Reference)
387 */
388 public void mapReference(Block.Reference input, Block.Reference output) {
389 Objects.requireNonNull(input);
390 Objects.requireNonNull(output);
391
392 if (output.target.isBuilt()) {
393 throw new IllegalArgumentException("Output block reference's target block is built\n"
394 + output.target.diagnosticText("reference target"));
395 }
396
397 for (Value outputArgument : output.arguments()) {
398 if (outputArgument.isBuilt()) {
399 throw new IllegalArgumentException("Output block reference argument's declaring block is built\n"
400 + outputArgument.block.diagnosticText(outputArgument, "reference argument"));
401 }
402 }
403
404 if (referenceMap == EMPTY_MAP) {
405 referenceMap = new HashMap<>();
406 }
407 referenceMap.put(input, output);
408 }
409
410 /**
411 * Returns the output block reference mapped to the given input block reference, if present, otherwise creates
412 * and returns a new output block reference from the input block reference.
413 * <p>
414 * If a mapping for the input block reference is present in this context, the mapped output block reference is
415 * returned. Otherwise, a new output block reference is created by obtaining the output block builder mapped to the
416 * input reference's target block, and creating a reference from the output block builder with arguments that are
417 * the output values mapped to the input reference's arguments. The newly created output block reference is not
418 * added to this context.
419 * <p>
420 * The output block reference and the output block builder are looked up only in this context. The output values for
421 * the input reference's arguments are looked up as specified by {@link #getValues(List)}.
422 *
423 * @param input the input block reference
424 * @return the output block reference, if present, otherwise a newly created output block reference
425 * @throws IllegalArgumentException if a new reference is to be created and there is no block mapping for the
426 * input's target block or there is no value mapping for an input's argument
427 * @see #queryReference(Block.Reference)
428 */
429 public Block.Reference getReferenceOrCreate(Block.Reference input) {
430 Optional<Block.Reference> optionalOutput = queryReference(input);
431 if (optionalOutput.isPresent()) {
432 return optionalOutput.get();
433 }
434
435 // Create reference
436 Block.Builder outputBlock = blockMap.get(input.targetBlock());
437 if (outputBlock == null) {
438 throw new IllegalArgumentException(
439 "No mapping for input block reference's target block\n"
440 + input.target.diagnosticText("reference target"));
441 }
442 return outputBlock.reference(getValues(input.arguments()));
443 }
444
445
446 // Properties mappings
447
448 /**
449 * {@return an object associated with a property key, or {@code null} if not associated.}
450 * <p>
451 * The property is looked up first in this context and, if absent, in the parent context, if present, and so on
452 * until a mapping is found or there is no parent context.
453 *
454 * @param key the property key
455 * @see #putProperty(Object, Object)
456 */
457 public Object getProperty(Object key) {
458 Objects.requireNonNull(key);
459
460 CodeContext p = this;
461 do {
462 Object value = p.propertiesMap.get(key);
463 if (value != null) {
464 return value;
465 }
466 p = p.parent;
467 } while (p != null);
468
469 return null;
470 }
471
472 /**
473 * Associates an object with a property key.
474 * <p>
475 * The property is added only to this context.
476 *
477 * @param key the property key
478 * @param value the associated object
479 * @return the previous associated object, or {@code null} if not associated
480 * @see #getProperty(Object)
481 */
482 public Object putProperty(Object key, Object value) {
483 Objects.requireNonNull(key);
484 Objects.requireNonNull(value);
485
486 if (propertiesMap == EMPTY_MAP) {
487 propertiesMap = new HashMap<>();
488 }
489 return propertiesMap.put(key, value);
490 }
491
492 /**
493 * If the property key is not already associated with an object, attempts to compute the object using the
494 * mapping function and associates it.
495 * <p>
496 * This method first obtains the property value using {@link #getProperty(Object)}. If no property is found, a new
497 * non-{@code null} property value is computed and added only to this context.
498 *
499 * @param key the property key
500 * @param mappingFunction the mapping function
501 * @return the current (existing or computed) object associated with the property key
502 * @see #getProperty(Object)
503 * @see #putProperty(Object, Object)
504 */
505 public Object computePropertyIfAbsent(Object key, Function<Object, Object> mappingFunction) {
506 Objects.requireNonNull(key);
507 Objects.requireNonNull(mappingFunction);
508
509 if (propertiesMap == EMPTY_MAP) {
510 propertiesMap = new HashMap<>();
511 }
512 Object value = getProperty(key);
513 if (value != null) {
514 return value;
515 }
516 value = Objects.requireNonNull(mappingFunction.apply(key));
517 propertiesMap.put(key, value);
518 return value;
519 }
520
521
522 // Factories
523
524 /**
525 * {@return a new root context initialized with no local mappings, no properties, and no parent context.}
526 * @see #create(CodeContext)
527 */
528 public static CodeContext create() {
529 return new CodeContext(null);
530 }
531
532 /**
533 * {@return a new child context initialized with the given parent context, no local mappings, and no properties.}
534 * <p>
535 * The returned context looks up value mappings and properties first in itself, and then in its parent context,
536 * and so on, until a mapping is found or there is no parent context.
537 * <p>
538 * Block and block reference mappings are local to the returned context and are not looked up in parent contexts.
539 * <p>
540 * Mappings and properties added to the returned context are added only to that context.
541 *
542 * @param parent the parent code context
543 */
544 public static CodeContext create(CodeContext parent) {
545 Objects.requireNonNull(parent);
546 return new CodeContext(parent);
547 }
548 }