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 }