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 }