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 }