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 com.sun.tools.javac.api.JavacScope;
 29 import com.sun.tools.javac.api.JavacTrees;
 30 import com.sun.tools.javac.code.Symbol.ClassSymbol;
 31 import com.sun.tools.javac.comp.Attr;
 32 import com.sun.tools.javac.model.JavacElements;
 33 import com.sun.tools.javac.processing.JavacProcessingEnvironment;
 34 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
 35 import com.sun.tools.javac.tree.TreeMaker;
 36 import com.sun.tools.javac.util.Context;
 37 import jdk.incubator.code.dialect.core.CoreType;
 38 import jdk.incubator.code.dialect.java.JavaOp;
 39 import jdk.incubator.code.internal.ReflectMethods;
 40 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
 41 import jdk.incubator.code.dialect.core.FunctionType;
 42 import jdk.incubator.code.dialect.java.MethodRef;
 43 import jdk.incubator.code.extern.OpWriter;
 44 import jdk.internal.access.SharedSecrets;
 45 
 46 import javax.annotation.processing.ProcessingEnvironment;
 47 import javax.lang.model.element.ExecutableElement;
 48 import javax.lang.model.element.Modifier;
 49 import java.lang.reflect.Method;
 50 import java.lang.reflect.Proxy;
 51 import java.util.*;
 52 import java.util.function.BiFunction;
 53 
 54 /**
 55  * An operation modelling a unit of program behaviour.
 56  * <p>
 57  * An operation might model the addition of two integers, or a method invocation expression.
 58  * Alternatively an operation may model something more complex like method declarations, lambda expressions, or
 59  * try statements. In such cases an operation will contain one or more bodies modelling the nested structure.
 60  * <p>
 61  * An instance of an operation when initially constructed is referred to as an unbuilt operation.
 62  * An unbuilt operation's state and descendants are all immutable except for its {@link #result result} and
 63  * {@link #parent parent}, which are initially set to {@code null}.
 64  * <p>
 65  * An unbuilt operation transitions to a built operation in one of two ways:
 66  * <ol>
 67  * <li>
 68  * {@link #buildAsRoot() building} the unbuilt operation to become a built {@link #isRoot() root} operation. The
 69  * operation's {@link #result result} and {@link #parent parent} are always {@code null}.
 70  * </li>
 71  * <li>
 72  * {@link Block.Builder#op(Op) appending} the unbuilt operation to a block builder to first become an unbuilt-bound
 73  * operation that is bound to an operation result and parent block.
 74  * The unbuilt-bound operation has a non-{@code null} unbuilt {@link #result result} that never changes, an unbuilt
 75  * value that can be used by subsequent constructed operations.
 76  * An unbuilt-bound operation transitions to a built bound operation when the block builder it was appended to builds
 77  * the block, after which the built bound operation has a non-{@code null} {@link #parent parent} that never changes and
 78  * a built {@link #result}.
 79  * Before then the unbuilt-bound operation's {@link #parent parent} is inaccessible, as is unbuilt result's
 80  * {@link Value#declaringBlock() declaring block}) (since both refer to the same block).
 81  * </li>
 82  * </ol>
 83  * A built operation is fully immutable either as a root operation, the root of a code model, or as a bound operation
 84  * within a code model.
 85  * <p>
 86  * An operation can only be constructed with unbuilt values as operands (if any) and unbuilt block references as
 87  * successors (if any), otherwise construction fails with an exception.
 88  */
 89 public non-sealed abstract class Op implements CodeElement<Op, Body> {
 90 
 91     /**
 92      * An operation characteristic indicating the operation is pure and has no side effects.
 93      */
 94     public interface Pure {
 95     }
 96 
 97     /**
 98      * An operation characteristic indicating the operation has one or more bodies.
 99      */
100     public interface Nested {
101         /**
102          * {@return the bodies of the nested operation.}
103          */
104         List<Body> bodies();
105     }
106 
107     /**
108      * An operation characteristic indicating the operation represents a loop
109      */
110     public interface Loop extends Nested {
111         /**
112          * {@return the body of the loop operation.}
113          */
114         Body loopBody();
115     }
116 
117     /**
118      * An operation characteristic indicating the operation has one or more bodies,
119      * all of which are isolated and capture no values.
120      */
121     public interface Isolated extends Nested {
122     }
123 
124     /**
125      * An operation characteristic indicating the operation is invokable.
126      */
127     public interface Invokable extends Nested {
128         /**
129          * {@return the body of the invokable operation.}
130          */
131         Body body();
132 
133         /**
134          * {@return the function type describing the invokable operation's parameter types and return type.}
135          */
136         FunctionType invokableType();
137 
138         /**
139          * {@return the entry block parameters of this operation's body}
140          */
141         default List<Block.Parameter> parameters() {
142             return body().entryBlock().parameters();
143         }
144 
145         /**
146          * Computes values captured by this invokable operation's body.
147          *
148          * @return the captured values.
149          * @see Body#capturedValues()
150          */
151         default List<Value> capturedValues() {
152             return List.of();
153         }
154     }
155 
156     /**
157      * An operation characteristic indicating the operation can replace itself with a lowered form.
158      */
159     // @@@ Hide this abstraction within JavaOp?
160     public interface Lowerable {
161 
162         /**
163          * Lowers this operation into the block builder, commonly replacing nested structure
164          * with interconnected basic blocks. The previous lowering code transformation
165          * is used to compose with a lowering transformation that is applied to bodies
166          * of this operation, ensuring lowering is applied consistently to nested content.
167          *
168          * @param b the block builder
169          * @param opT the previous lowering code transformation, may be {@code null}
170          * @return the block builder to use for further building
171          */
172         Block.Builder lower(Block.Builder b, CodeTransformer opT);
173 
174         /**
175          * Returns a composed code transformer that composes with an operation transformer function adapted to lower
176          * operations.
177          * <p>
178          * This method behaves as if it returns the result of the following expression:
179          * {@snippet lang = java:
180          * CodeTransformer.andThen(before, lowering(before, f));
181          *}
182          *
183          * @param before the code transformer to apply before
184          * @param f the operation transformer function to apply after
185          * @return the composed code transformer
186          */
187         static CodeTransformer andThenLowering(CodeTransformer before, BiFunction<Block.Builder, Op, Block.Builder> f) {
188             return CodeTransformer.andThen(before, lowering(before, f));
189         }
190 
191         /**
192          * Returns an adapted operation transformer function that adapts an operation transformer function
193          * {@code f} to also transform lowerable operations.
194          * <p>
195          * The adapted operation transformer function first applies a block builder and operation
196          * to the operation transformer function {@code f}.
197          * If the result is not {@code null} then the result is returned.
198          * Otherwise, if the operation is a lowerable operation then the result of applying the
199          * block builder and code transformer {@code before} to {@link Lowerable#lower lower}
200          * of the lowerable operation is returned.
201          * Otherwise, the operation is copied by applying it to {@link Block.Builder#op op} of the block builder,
202          * and the block builder is returned.
203          *
204          * @param before the code transformer to apply for lowering
205          * @param f the operation transformer function to apply after
206          * @return the adapted operation transformer function
207          */
208         static BiFunction<Block.Builder, Op, Block.Builder> lowering(CodeTransformer before, BiFunction<Block.Builder, Op, Block.Builder> f) {
209             return (block, op) -> {
210                 Block.Builder b = f.apply(block, op);
211                 if (b == null) {
212                     if (op instanceof Lowerable lop) {
213                         block = lop.lower(block, before);
214                     } else {
215                         block.op(op);
216                     }
217                 } else {
218                     block = b;
219                 }
220                 return block;
221             };
222         }
223     }
224 
225     /**
226      * An operation characteristic indicating the operation is a terminating operation
227      * occurring as the last operation in a block.
228      * <p>
229      * A terminating operation passes control to either another block within the same parent body
230      * or to that parent body.
231      */
232     public interface Terminating {
233     }
234 
235     /**
236      * An operation characteristic indicating the operation is a body terminating operation
237      * occurring as the last operation in a block.
238      * <p>
239      * A body terminating operation passes control back to its nearest ancestor body.
240      */
241     public interface BodyTerminating extends Terminating {
242     }
243 
244     /**
245      * An operation characteristic indicating the operation is a block terminating operation
246      * occurring as the last operation in a block.
247      * <p>
248      * The operation has one or more successors to other blocks within the same parent body, and passes
249      * control to one of those blocks.
250      */
251     public interface BlockTerminating extends Terminating {
252         /**
253          * {@return the list of successor blocks associated with this block terminating operation}
254          */
255         List<Block.Reference> successors();
256     }
257 
258     /**
259      * A value that is the result of an operation.
260      */
261     public static final class Result extends Value {
262 
263         /**
264          * If assigned to an operation result, it indicates the operation is a root operation
265         */
266         private static final Result ROOT_RESULT = new Result();
267 
268         final Op op;
269 
270         private Result() {
271             // Constructor for instance of ROOT_RESULT
272             super(null, null);
273             this.op = null;
274         }
275 
276         Result(Block block, Op op) {
277             super(block, op.resultType());
278 
279             this.op = op;
280         }
281 
282         @Override
283         public String toString() {
284             return "%result@" + Integer.toHexString(hashCode());
285         }
286 
287         @Override
288         public SequencedSet<Value> dependsOn() {
289             SequencedSet<Value> depends = new LinkedHashSet<>(op.operands());
290             if (op instanceof Terminating) {
291                 op.successors().stream().flatMap(h -> h.arguments().stream()).forEach(depends::add);
292             }
293 
294             return Collections.unmodifiableSequencedSet(depends);
295         }
296 
297         /**
298          * {@return the result's declaring operation.}
299          */
300         public Op op() {
301             return op;
302         }
303     }
304 
305     /**
306      * Source location information for an operation.
307      *
308      * @param sourceRef the reference to the source, {@code null} if absent
309      * @param line the line in the source
310      * @param column the column in the source
311      */
312     public record Location(String sourceRef, int line, int column) {
313 
314         /**
315          * The location value, {@code null}, indicating no location information.
316          */
317         public static final Location NO_LOCATION = null;
318 
319         /**
320          * Constructions a location with line and column only.
321          *
322          * @param line the line in the source
323          * @param column the column in the source
324          */
325         public Location(int line, int column) {
326             this(null, line, column);
327         }
328     }
329 
330     // Set when op is unbuilt-bound or root, otherwise null when unbuilt
331     // @@@ stable value?
332     Result result;
333 
334     // null if not specified
335     // @@@ stable value?
336     Location location;
337 
338     final List<Value> operands;
339 
340     /**
341      * Constructs an operation from a given operation.
342      * <p>
343      * The constructor defers to the {@link Op#Op(List) operands} constructor passing a list of values computed, in
344      * order, by mapping the given operation's operands using the code context. The constructor also assigns the new
345      * operation's location to the given operation's location, if any.
346      *
347      * @param that the given operation.
348      * @param cc   the code context.
349      */
350     protected Op(Op that, CodeContext cc) {
351         List<Value> outputOperands = cc.getValues(that.operands);
352         // Values should be guaranteed to connect to unbuilt blocks since
353         // the context only allows such mappings, assert for clarity
354         assert outputOperands.stream().noneMatch(Value::isBuilt);
355         this.operands = List.copyOf(outputOperands);
356         this.location = that.location;
357     }
358 
359     /**
360      * Copies this operation and transforms its bodies, if any.
361      * <p>
362      * Bodies are {@link Body#transform(CodeContext, CodeTransformer) transformed} with the given code context and
363      * code transformer.
364      * @apiNote
365      * To copy an operation use the {@link CodeTransformer#COPYING_TRANSFORMER copying transformer}.
366      *
367      * @param cc the code context.
368      * @param ot the code transformer.
369      * @return the transformed operation.
370      * @see CodeTransformer#COPYING_TRANSFORMER
371      */
372     public abstract Op transform(CodeContext cc, CodeTransformer ot);
373 
374     /**
375      * Constructs an operation with a list of operands.
376      *
377      * @param operands the list of operands, a copy of the list is performed if required.
378      * @throws IllegalArgumentException if an operand is built because its declaring block is built.
379      */
380     protected Op(List<? extends Value> operands) {
381         for (Value operand : operands) {
382             if (operand.isBuilt()) {
383                 throw new IllegalArgumentException("Operand's declaring block is built: " + operand);
384             }
385         }
386         this.operands = List.copyOf(operands);
387     }
388 
389     /**
390      * Sets the originating source location of this operation, if this operation is not built.
391      *
392      * @param l the location, the {@link Location#NO_LOCATION} value indicates the location is not specified.
393      * @throws IllegalStateException if this operation is built.
394      */
395     public final void setLocation(Location l) {
396         // @@@ Fail if location != null?
397         if (isRoot() || (result != null && result.block.isBuilt())) {
398             throw new IllegalStateException("Built operation");
399         }
400 
401         location = l;
402     }
403 
404     /**
405      * {@return the originating source location of this operation, otherwise {@code null} if not specified}
406      */
407     public final Location location() {
408         return location;
409     }
410 
411     /**
412      * Returns this operation's parent block, otherwise {@code null} if this operation is unbuilt or a root.
413      * <p>
414      * The operation's parent block is the same as the operation result's {@link Value#declaringBlock declaring block}.
415      *
416      * @return operation's parent block, or {@code null} if this operation is unbuilt or a root.
417      * @throws IllegalStateException if this operation is unbuilt-bound.
418      * @see Value#declaringBlock()
419      */
420     @Override
421     public final Block parent() {
422         if (isRoot() || result == null) {
423             return null;
424         }
425 
426         if (!result.block.isBuilt()) {
427             throw new IllegalStateException("Unbuilt-bound operation");
428         }
429 
430         return result.block;
431     }
432 
433     @Override
434     public final List<Body> children() {
435         return bodies();
436     }
437 
438     /**
439      * {@return the operation's bodies, as an unmodifiable list}
440      * @implSpec this implementation returns an unmodifiable empty list.
441      * @see #children()
442      */
443     public List<Body> bodies() {
444         return List.of();
445     }
446 
447     /**
448      * {@return the operation's result type}
449      */
450     public abstract TypeElement resultType();
451 
452 
453     /**
454      * Returns the operation's result, otherwise {@code null} if this operation is unbuilt or a
455      * root.
456      *
457      * @return the operation's result, or {@code null} if this operation is unbuilt or a root.
458      */
459     public final Result result() {
460         return result == Result.ROOT_RESULT ? null : result;
461     }
462 
463     /**
464      * {@return the operation's operands, as an unmodifiable list}
465      */
466     public final List<Value> operands() {
467         return operands;
468     }
469 
470     /**
471      * {@return the operation's successors, as an unmodifiable list}
472      * @implSpec this implementation returns an unmodifiable empty list.
473      */
474     public List<Block.Reference> successors() {
475         return List.of();
476     }
477 
478     /**
479      * Returns the operation's function type.
480      * <p>
481      * The function type's result type is the operation's result type and its parameter types are the
482      * operation's operand types, in order.
483      *
484      * @return the function type
485      */
486     public final FunctionType opType() {
487         List<TypeElement> operandTypes = operands.stream().map(Value::type).toList();
488         return CoreType.functionType(resultType(), operandTypes);
489     }
490 
491     /**
492      * Computes values captured by this operation. A captured value is a value that is used
493      * but not declared by any descendant operation of this operation.
494      * <p>
495      * The order of the captured values is first use encountered in depth
496      * first search of this operation's descendant operations.
497      *
498      * @return the list of captured values, modifiable
499      * @see Body#capturedValues()
500      */
501     public final List<Value> capturedValues() {
502         Set<Value> cvs = new LinkedHashSet<>();
503 
504         Deque<Body> bodyStack = new ArrayDeque<>();
505         for (Body childBody : bodies()) {
506             Body.capturedValues(cvs, bodyStack, childBody);
507         }
508         return new ArrayList<>(cvs);
509     }
510 
511     /**
512      * Builds this operation to become a built root operation. After this operation is built its
513      * {@link #result result} and {@link #parent parent} will always be {@code null}.
514      * <p>
515      * This method is idempotent.
516      *
517      * @throws IllegalStateException if this operation is unbuilt-bound.
518      * @see #isRoot()
519      */
520     public final void buildAsRoot() {
521         if (result == Result.ROOT_RESULT) {
522             return;
523         }
524         if (result != null) {
525             throw new IllegalStateException("Operation is unbuilt-bound to a parent block");
526         }
527         result = Result.ROOT_RESULT;
528     }
529 
530     /**
531      * {@return {@code true} if this operation is a root operation.}
532      * @see #buildAsRoot()
533      * @see #isBound()
534      * */
535     public final boolean isRoot() {
536         return result == Result.ROOT_RESULT;
537     }
538 
539     /**
540      * {@return {@code true} if this operation is a bound to a result and parent block.}
541      * @see #buildAsRoot()
542      * @see #isRoot()
543      * */
544     public final boolean isBound() {
545         return !isRoot() && result != null;
546     }
547 
548     /**
549      * Externalizes this operation's name as a string.
550      * @implSpec this implementation returns the result of the expression {@code this.getClass().getName()}.
551      *
552      * @return the operation name
553      */
554     public String externalizeOpName() {
555         return this.getClass().getName();
556     }
557 
558     /**
559      * Externalizes this operation's specific state as a map of attributes.
560      *
561      * <p>A null attribute value is represented by the constant
562      * value {@link jdk.incubator.code.extern.ExternalizedOp#NULL_ATTRIBUTE_VALUE}.
563      * @implSpec this implementation returns an unmodifiable empty map.
564      *
565      * @return the operation's externalized state, as an unmodifiable map
566      */
567     public Map<String, Object> externalize() {
568         return Map.of();
569     }
570 
571     /**
572      * Returns the textual form of this operation.
573      *
574      * @return the textual form of this operation.
575      */
576     public final String toText() {
577         return OpWriter.toText(this);
578     }
579 
580 
581     /**
582      * Returns the code model of a reflectable lambda expression or method reference.
583      *
584      * @param fiInstance a functional interface instance that is the result of a reflectable lambda expression or
585      *                   method reference.
586      * @return the code model, or an empty optional if the functional interface instance is not the result of a
587      * reflectable lambda expression or method reference.
588      * @throws UnsupportedOperationException if the Java version used at compile time to generate and store the code
589      * model is not the same as the Java version used at runtime to load the code model.
590      * @apiNote if the functional interface instance is a proxy instance, then the code model is unavailable and this
591      * method returns an empty optional.
592      */
593     public static Optional<Quoted<JavaOp.LambdaOp>> ofLambda(Object fiInstance) {
594         Object oq = fiInstance;
595         if (Proxy.isProxyClass(oq.getClass())) {
596             // @@@ The interpreter implements interpretation of
597             // lambdas using a proxy whose invocation handler
598             // supports the internal protocol to access the quoted instance
599             oq = Proxy.getInvocationHandler(oq);
600         }
601 
602         Method method;
603         try {
604             method = oq.getClass().getDeclaredMethod("__internal_quoted");
605         } catch (NoSuchMethodException e) {
606             return Optional.empty();
607         }
608         method.setAccessible(true);
609 
610         Quoted<?> q;
611         try {
612             q = (Quoted<?>) method.invoke(oq);
613         } catch (ReflectiveOperationException e) {
614             // op method may throw UOE in case java compile time version doesn't match runtime version
615             if (e.getCause() instanceof UnsupportedOperationException uoe) {
616                 throw uoe;
617             }
618             throw new RuntimeException(e);
619         }
620         if (!(q.op() instanceof JavaOp.LambdaOp)) {
621             // This can only happen if the stored model is invalid
622             throw new RuntimeException("Invalid code model for lambda expression : " + q);
623         }
624         @SuppressWarnings("unchecked")
625         Quoted<JavaOp.LambdaOp> lq = (Quoted<JavaOp.LambdaOp>) q;
626         return Optional.of(lq);
627     }
628 
629     /**
630      * Returns the code model of a reflectable method.
631      *
632      * @param method the method.
633      * @return the code model, or an empty optional if the method is not reflectable.
634      * @throws UnsupportedOperationException if the Java version used at compile time to generate and store the code
635      * model is not the same as the Java version used at runtime to load the code model.
636      */
637     // @@@ Make caller sensitive with the same access control as invoke
638     // and throwing IllegalAccessException
639     // @CallerSensitive
640     @SuppressWarnings("unchecked")
641     public static Optional<FuncOp> ofMethod(Method method) {
642         return (Optional<FuncOp>)SharedSecrets.getJavaLangReflectAccess()
643                 .setCodeModelIfNeeded(method, Op::createCodeModel);
644     }
645 
646     private static Optional<FuncOp> createCodeModel(Method method) {
647         char[] sig = MethodRef.method(method).toString().toCharArray();
648         for (int i = 0; i < sig.length; i++) {
649             switch (sig[i]) {
650                 case '.', ';', '[', '/': sig[i] = '$';
651             }
652         }
653         String opMethodName = new String(sig);
654         Method opMethod;
655         try {
656             // @@@ Use method handle with full power mode
657             opMethod = method.getDeclaringClass().getDeclaredMethod(opMethodName);
658         } catch (NoSuchMethodException e) {
659             return Optional.empty();
660         }
661         opMethod.setAccessible(true);
662         try {
663             FuncOp funcOp = (FuncOp) opMethod.invoke(null);
664             return Optional.of(funcOp);
665         } catch (ReflectiveOperationException e) {
666             // op method may throw UOE in case java compile time version doesn't match runtime version
667             if (e.getCause() instanceof UnsupportedOperationException uoe) {
668                 throw uoe;
669             }
670             throw new RuntimeException(e);
671         }
672     }
673 
674     /**
675      * Returns the code model of provided executable element (if any).
676      * <p>
677      * If the executable element has a code model then it will be an instance of
678      * {@code java.lang.reflect.code.op.CoreOps.FuncOp}.
679      * Note: due to circular dependencies we cannot refer to the type explicitly.
680      *
681      * @param processingEnvironment the annotation processing environment
682      * @param e the executable element.
683      * @return the code model of the provided executable element (if any).
684      */
685     public static Optional<FuncOp> ofElement(ProcessingEnvironment processingEnvironment, ExecutableElement e) {
686         if (e.getModifiers().contains(Modifier.ABSTRACT) ||
687                 e.getModifiers().contains(Modifier.NATIVE)) {
688             return Optional.empty();
689         }
690 
691         Context context = ((JavacProcessingEnvironment)processingEnvironment).getContext();
692         ReflectMethods reflectMethods = ReflectMethods.instance(context);
693         Attr attr = Attr.instance(context);
694         JavacElements elements = JavacElements.instance(context);
695         JavacTrees javacTrees = JavacTrees.instance(context);
696         TreeMaker make = TreeMaker.instance(context);
697         try {
698             JCMethodDecl methodTree = (JCMethodDecl)elements.getTree(e);
699             JavacScope scope = javacTrees.getScope(javacTrees.getPath(e));
700             ClassSymbol enclosingClass = (ClassSymbol) scope.getEnclosingClass();
701             FuncOp op = attr.runWithAttributedMethod(scope.getEnv(), methodTree,
702                     attribBlock -> {
703                         try {
704                             return reflectMethods.getMethodBody(enclosingClass, methodTree, attribBlock, make);
705                         } catch (Throwable ex) {
706                             // this might happen if the source code contains errors
707                             return null;
708                         }
709                     });
710             return Optional.ofNullable(op);
711         } catch (RuntimeException ex) {  // ReflectMethods.UnsupportedASTException
712             // some other error occurred when attempting to attribute the method
713             // @@@ better report of error
714             ex.printStackTrace();
715             return Optional.empty();
716         }
717     }
718 }