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 the output be unbuilt, specifically blocks
 46  * connected to the output are unbuilt. An output value is an unbuilt value whose declaring block is
 47  * unbuilt. An output block builder is a block builder that is unbuilt and therefore its block is unbuilt. An
 48  * output block reference is an unbuilt block reference whose target block is unbuilt with unbuilt arguments whose
 49  * declaring blocks are unbuilt.
 50  * <p>
 51  * Unless otherwise specified the passing of a {@code null} argument to the methods of this interface results in a
 52  * {@code NullPointerException}.
 53  */
 54 public final class CodeContext {
 55 
 56     private static final Map<?, ?> EMPTY_MAP = Map.of();
 57 
 58     @SuppressWarnings("unchecked")
 59     private static <K, V> Map<K, V> emptyMap() {
 60         return (Map<K, V>) EMPTY_MAP;
 61     }
 62 
 63     private final CodeContext parent;
 64     private Map<Value, Value> valueMap;
 65     private Map<Block, Block.Builder> blockMap;
 66     private Map<Block.Reference, Block.Reference> successorMap;
 67     private Map<Object, Object> propertiesMap;
 68 
 69     private CodeContext(CodeContext that) {
 70         this.parent = that;
 71         this.blockMap = emptyMap();
 72         this.valueMap = emptyMap();
 73         this.successorMap = emptyMap();
 74         this.propertiesMap = emptyMap();
 75     }
 76 
 77     /**
 78      * {@return the parent context, otherwise {@code null} of there is no parent context.}
 79      */
 80     public CodeContext parent() {
 81         return parent;
 82     }
 83 
 84     // Value mappings
 85 
 86     /**
 87      * {@return the output value mapped to the input value}
 88      * <p>
 89      * If this context is not isolated and there is no value mapping in this context then this method will return
 90      * the result of calling {@code getValue} on the parent context, if present. Otherwise, if this context is isolated
 91      * or there is no parent context, then there is no mapping.
 92      *
 93      * @param input the input value
 94      * @throws IllegalArgumentException if there is no mapping
 95      */
 96     public Value getValue(Value input) {
 97         Value output = getValueOrNull(input);
 98         if (output != null) {
 99             return output;
100         }
101         throw new IllegalArgumentException("No mapping for input value: " + input);
102     }
103 
104     /**
105      * {@return the output value mapped to the input value or a default value if no mapping}
106      *
107      * @param input the input value
108      * @param defaultValue the default value to return if no mapping
109      */
110     public Value getValueOrDefault(Value input, Value defaultValue) {
111         Value output = getValueOrNull(input);
112         if (output != null) {
113             return output;
114         }
115         return defaultValue;
116     }
117 
118     /**
119      * Returns a list of mapped output values by obtaining, in order, the output value for each element in the list
120      * of input values.
121      *
122      * @param inputs the list of input values
123      * @return a modifiable list of output values
124      * @throws IllegalArgumentException if an input value has no mapping
125      */
126     // @@@ If getValue is modified to return null then this method should fail on null
127     public List<Value> getValues(List<? extends Value> inputs) {
128         return inputs.stream().map(this::getValue).collect(toList());
129     }
130 
131     private Value getValueOrNull(Value input) {
132         Objects.requireNonNull(input);
133 
134         CodeContext p = this;
135         do {
136             Value output = p.valueMap.get(input);
137             if (output != null) {
138                 return output;
139             }
140             p = p.parent;
141         } while (p != null);
142 
143         return null;
144     }
145 
146     /**
147      * Maps an input value to an output value.
148      * <p>
149      * Uses of the input value will be mapped to the output value when transforming.
150      *
151      * @param input the input value
152      * @param output the output value
153      * @throws IllegalArgumentException if the output value's declaring block is built
154      */
155     public void mapValue(Value input, Value output) {
156         Objects.requireNonNull(input);
157         Objects.requireNonNull(output);
158 
159         if (output.isBuilt()) {
160             throw new IllegalArgumentException("Output value bound: " + output);
161         }
162 
163         if (valueMap == EMPTY_MAP) {
164             valueMap = new HashMap<>();
165         }
166         valueMap.put(input, output);
167     }
168 
169     /**
170      * Maps an input value to an output value, if no such mapping exists.
171      * <p>
172      * Uses of the input value will be mapped to the output value when transforming.
173      *
174      * @param input the input value
175      * @param output the output value
176      * @return the previous mapped value, or null of there was no mapping.
177      * @throws IllegalArgumentException if the output value's declaring block is built
178      */
179     // @@@ Is this needed?
180     public Value mapValueIfAbsent(Value input, Value output) {
181         Objects.requireNonNull(input);
182         Objects.requireNonNull(output);
183 
184         if (output.isBuilt()) {
185             throw new IllegalArgumentException("Output value is bound: " + output);
186         }
187 
188         if (valueMap == EMPTY_MAP) {
189             valueMap = new HashMap<>();
190         }
191         return valueMap.putIfAbsent(input, output);
192     }
193 
194     /**
195      * Maps the list of input values, in order, to the corresponding list of output values, up to the number of
196      * elements that is the minimum of the size of both lists.
197      * <p>
198      * Uses of an input value will be mapped to the corresponding output value when transforming.
199      *
200      * @param inputs the input values
201      * @param outputs the output values.
202      * @throws IllegalArgumentException if an output value's declaring block is built
203      */
204     public void mapValues(List<? extends Value> inputs, List<? extends Value> outputs) {
205         // @@@ sizes should be the same?
206         for (int i = 0; i < Math.min(inputs.size(), outputs.size()); i++) {
207             mapValue(inputs.get(i), outputs.get(i));
208         }
209     }
210 
211 
212     // Block mappings
213 
214     /**
215      * {@return the output block builder mapped to the input block, otherwise null if no mapping}
216      *
217      * @param input the input block
218      */
219     // @@@ throw IllegalArgumentException if there is no mapping?
220     public Block.Builder getBlock(Block input) {
221         Objects.requireNonNull(input);
222 
223         return blockMap.get(input);
224     }
225 
226     /**
227      * Maps an input block to an output block builder.
228      * <p>
229      * Uses of the input block will be mapped to the output block builder when transforming.
230      *
231      * @param input the input block
232      * @param output the output block builder
233      * @throws IllegalArgumentException if the output block builder's block is built
234      */
235     public void mapBlock(Block input, Block.Builder output) {
236         Objects.requireNonNull(input);
237         Objects.requireNonNull(output);
238 
239         if (output.target().isBuilt()) {
240             throw new IllegalArgumentException("Output block builder is built: " + output);
241         }
242 
243         if (blockMap == EMPTY_MAP) {
244             blockMap = new HashMap<>();
245         }
246         blockMap.put(input, output);
247     }
248 
249 
250     // Successor mappings
251 
252     /**
253      * {@return the output block reference mapped to the input block reference,
254      * otherwise null if no mapping}
255      *
256      * @param input the input reference
257      */
258     // @@@ throw IllegalArgumentException if there is no mapping?
259     public Block.Reference getSuccessor(Block.Reference input) {
260         Objects.requireNonNull(input);
261 
262         return successorMap.get(input);
263     }
264 
265     /**
266      * Maps an input block reference to an output block reference.
267      * <p>
268      * Uses of the input block reference will be mapped to the output block reference when transforming.
269      *
270      * @param input the input block reference
271      * @param output the output block reference
272      * @throws IllegalArgumentException if the output block reference's target block is built or any of the
273      * reference's arguments declaring blocks are built.
274      */
275     public void mapSuccessor(Block.Reference input, Block.Reference output) {
276         Objects.requireNonNull(input);
277         Objects.requireNonNull(output);
278 
279         if (output.target.isBuilt()) {
280             throw new IllegalArgumentException("Output block reference target is built: " + output);
281         }
282 
283         for (Value outputArgument : output.arguments()) {
284             if (outputArgument.isBuilt()) {
285                 throw new IllegalArgumentException("Output block reference argument is bound: " + outputArgument);
286             }
287         }
288 
289         if (successorMap == EMPTY_MAP) {
290             successorMap = new HashMap<>();
291         }
292         successorMap.put(input, output);
293     }
294 
295     /**
296      * Returns a mapped output block reference, if present, otherwise creates a new, unmapped, reference from the input
297      * block reference.
298      * <p>
299      * A new, unmapped reference, is created by obtaining the mapped output block builder from the input reference's
300      * target block, and creating a successor from the output block builder with arguments that is the result of
301      * obtaining the mapped values from the input reference's arguments.
302      *
303      * @param input the input block reference
304      * @return the output block reference, if present, otherwise a created block reference
305      * @throws IllegalArgumentException if a new reference is to be created and there is no block mapping for the
306      * input's target block or there is no value mapping for an input's argument
307      */
308     public Block.Reference getSuccessorOrCreate(Block.Reference input) {
309         Block.Reference successor = getSuccessor(input);
310         if (successor != null) {
311             return successor;
312         }
313 
314         // Create successor
315         Block.Builder outputBlock = getBlock(input.targetBlock());
316         if (outputBlock == null) {
317             throw new IllegalArgumentException("No mapping for input reference target block" + input.targetBlock());
318         }
319         return outputBlock.successor(getValues(input.arguments()));
320     }
321 
322 
323     // Properties mappings
324 
325     /**
326      * {@return an object associated with a property key, or null if not associated.}
327      *
328      * @param key the property key
329      */
330     public Object getProperty(Object key) {
331         CodeContext p = this;
332         do {
333             Object value = p.propertiesMap.get(key);
334             if (value != null) {
335                 return value;
336             }
337             p = p.parent;
338         } while (p != null);
339 
340         return null;
341     }
342 
343     /**
344      * Associates an object with a property key.
345      *
346      * @param key the property key
347      * @param value the associated object
348      * @return the current associated object, or null if not associated
349      */
350     public Object putProperty(Object key, Object value) {
351         if (propertiesMap == EMPTY_MAP) {
352             propertiesMap = new HashMap<>();
353         }
354         return propertiesMap.put(key, value);
355     }
356 
357     /**
358      * If the property key is not already associated with an object, attempts to compute the object using the
359      * mapping function and associates it unless {@code null}.
360      *
361      * @param key the property key
362      * @param mappingFunction the mapping function
363      * @return the current (existing or computed) object associated with the property key,
364      * or null if the computed object is null
365      */
366     public Object computePropertyIfAbsent(Object key, Function<Object, Object> mappingFunction) {
367         if (propertiesMap == EMPTY_MAP) {
368             propertiesMap = new HashMap<>();
369         }
370         Object value = getProperty(key);
371         if (value != null) {
372             return value;
373         }
374         propertiesMap.put(key, value = mappingFunction.apply(key));
375         return value;
376     }
377 
378 
379     // Factories
380 
381     /**
382      * {@return a new isolated context initialized with no mappings and no parent.}
383      */
384     public static CodeContext create() {
385         return new CodeContext(null);
386     }
387 
388     /**
389      * {@return a new non-isolated context initialized with no mappings and a parent.}
390      * The returned context will query value and property mappings in its parent context
391      * if a query of its value and property mappings yields no result, and so on until
392      * a context has no parent context.
393      * <p>
394      * The returned context only queries its own block and block reference mappings
395      * and does not query the mappings in any ancestor context.
396      *
397      * @param parent the parent code context
398      */
399     public static CodeContext create(CodeContext parent) {
400         return new CodeContext(parent);
401     }
402 }