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 }