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.op;
 27 
 28 import java.lang.annotation.ElementType;
 29 import java.lang.annotation.Retention;
 30 import java.lang.annotation.RetentionPolicy;
 31 import java.lang.annotation.Target;
 32 import java.lang.invoke.MethodHandle;
 33 import java.lang.invoke.MethodHandles;
 34 import java.lang.reflect.Constructor;
 35 import java.lang.reflect.Method;
 36 import java.lang.reflect.Modifier;
 37 import jdk.incubator.code.Op;
 38 import java.util.HashMap;
 39 import java.util.Map;
 40 import java.util.function.Function;
 41 
 42 /**
 43  * An operation factory for constructing an {@link Op operation} from its
 44  * {@link ExternalizableOp.ExternalizedOp external content}.
 45  */
 46 @FunctionalInterface
 47 public interface OpFactory {
 48 
 49     /**
 50      * An operation declaration annotation.
 51      * <p>
 52      * This annotation may be declared on a concrete class implementing an {@link Op operation} whose name is a constant
 53      * that can be declared as this attribute's value.
 54      * <p>
 55      * Tooling can process declarations of this annotation to build a factory for constructing operations from their name.
 56      */
 57     @Retention(RetentionPolicy.RUNTIME)
 58     @Target(ElementType.TYPE)
 59     @interface OpDeclaration {
 60         /**
 61          * {@return the operation name}
 62          */
 63         String value();
 64     }
 65 
 66     /**
 67      * A class value for lazily computing an operation factory for {@link Op operation} classes
 68      * annotated with {@link OpDeclaration} and enclosed within a given class to compute over.
 69      * <p>
 70      * Each enclosed class annotated with {@code OpDeclaration} must declare a public static method named {@code create}
 71      * with one parameter type of {@link ExternalizableOp.ExternalizedOp} and return type that is the concrete class type.
 72      * Alternatively, the concrete class must declare public constructor with one parameter type of
 73      * {@link ExternalizableOp.ExternalizedOp}.
 74      */
 75     ClassValue<OpFactory> OP_FACTORY = new ClassValue<>() {
 76         @Override
 77         protected OpFactory computeValue(Class<?> c) {
 78             // @@@ See https://bugs.openjdk.org/browse/JDK-8321207
 79             final Map<String, Class<? extends Op>> opMapping = createOpMapping(c);
 80 
 81             return def -> {
 82                 var opClass = opMapping.get(def.name());
 83                 if (opClass == null) {
 84                     return null;
 85                 }
 86 
 87                 return constructOp(opClass, def);
 88             };
 89         }
 90     };
 91 
 92     /**
 93      * Constructs an {@link Op operation} from its external content.
 94      * <p>
 95      * If there is no mapping from the operation's name to a concrete
 96      * class of an {@code Op} then this method returns null.
 97      *
 98      * @param def the operation's external content
 99      * @return the operation, otherwise null
100      */
101     Op constructOp(ExternalizableOp.ExternalizedOp def);
102 
103     /**
104      * Constructs an {@link Op operation} from its external content.
105      * <p>
106      * If there is no mapping from the operation's name to a concrete
107      * class of an {@code Op} then this method throws UnsupportedOperationException.
108      *
109      * @param def the operation's external content
110      * @return the operation, otherwise null
111      * @throws UnsupportedOperationException if there is no mapping from the operation's
112      *                                       name to a concrete class of an {@code Op}
113      */
114     default Op constructOpOrFail(ExternalizableOp.ExternalizedOp def) {
115         Op op = constructOp(def);
116         if (op == null) {
117             throw new UnsupportedOperationException("Unsupported operation: " + def.name());
118         }
119 
120         return op;
121     }
122 
123     /**
124      * Compose this operation factory with another operation factory.
125      * <p>
126      * If there is no mapping in this operation factory then the result
127      * of the other operation factory is returned.
128      *
129      * @param after the other operation factory.
130      * @return the composed operation factory.
131      */
132     default OpFactory andThen(OpFactory after) {
133         return def -> {
134             Op op = constructOp(def);
135             return op != null ? op : after.constructOp(def);
136         };
137     }
138 
139     private static Map<String, Class<? extends Op>> createOpMapping(Class<?> opClasses) {
140         Map<String, Class<? extends Op>> mapping = new HashMap<>();
141         for (Class<?> opClass : opClasses.getNestMembers()) {
142             if (opClass.isAnnotationPresent(OpDeclaration.class)) {
143                 if (!Modifier.isPublic(opClass.getModifiers())) {
144                     throw new InternalError("Operation class not public: " + opClass.getName());
145                 }
146 
147                 if (!Op.class.isAssignableFrom(opClass)) {
148                     throw new InternalError("Operation class is not assignable to Op: " + opClass);
149                 }
150 
151                 MethodHandle handle = getOpConstructorMethodHandle(opClass);
152                 if (handle == null) {
153                     throw new InternalError("Operation constructor for operation class not found: " + opClass.getName());
154                 }
155 
156                 if (!Op.class.isAssignableFrom(handle.type().returnType())) {
157                     throw new InternalError("Operation constructor does not return an Op: " + handle);
158                 }
159 
160                 String opName = opClass.getAnnotation(OpDeclaration.class).value();
161                 @SuppressWarnings("unchecked")
162                 var opClassCast = (Class<Op>) opClass;
163                 mapping.put(opName, opClassCast);
164             }
165         }
166         return mapping;
167     }
168 
169     private static MethodHandle getOpConstructorMethodHandle(Class<?> opClass) {
170         Method method = null;
171         try {
172             method = opClass.getMethod("create", ExternalizableOp.ExternalizedOp.class);
173         } catch (NoSuchMethodException e) {
174         }
175 
176         if (method != null) {
177             if (!Modifier.isStatic(method.getModifiers())) {
178                 throw new InternalError("Operation constructor is not a static method: " + method);
179             }
180 
181             try {
182 /*__return MethodHandles.lookup().unreflect(method);__*/                return MethodHandles.publicLookup().unreflect(method);
183             } catch (IllegalAccessException e) {
184                 throw new InternalError("Inaccessible operation constructor for operation: " +
185                         method);
186             }
187         }
188 
189         Constructor<?> constructor;
190         try {
191             constructor = opClass.getConstructor(ExternalizableOp.ExternalizedOp.class);
192         } catch (NoSuchMethodException e) {
193             return null;
194         }
195 
196         try {
197 /*__return MethodHandles.lookup().unreflectConstructor(constructor);__*/            return MethodHandles.publicLookup().unreflectConstructor(constructor);
198         } catch (IllegalAccessException e) {
199             throw new InternalError("Inaccessible operation constructor for operation: " +
200                     constructor);
201         }
202     }
203 
204     private static Op constructOp(Class<? extends Op> opClass, ExternalizableOp.ExternalizedOp opDef) {
205         class Enclosed {
206             private static final ClassValue<Function<ExternalizableOp.ExternalizedOp, Op>> OP_CONSTRUCTOR = new ClassValue<>() {
207                 @Override
208                 protected Function<ExternalizableOp.ExternalizedOp, Op> computeValue(Class<?> opClass) {
209                     final MethodHandle opConstructorMH = getOpConstructorMethodHandle(opClass);
210                     assert opConstructorMH != null;
211 
212                     return operationDefinition -> {
213                         try {
214                             return (Op) opConstructorMH.invoke(operationDefinition);
215                         } catch (RuntimeException | Error e) {
216                             throw e;
217                         } catch (Throwable t) {
218                             throw new RuntimeException(t);
219                         }
220                     };
221                 }
222             };
223         }
224         return Enclosed.OP_CONSTRUCTOR.get(opClass).apply(opDef);
225     }
226 }